线程安全问题
提到多线程,就不得不提线程安全问题。线程安全问题发生的首要因素就是,多个线程操作共享变量。
只有在操作共享变量时才可能出现线程安全问题,对于局部变量的操作,是不会导致线程安全问题的。
注:本篇文章只讨论线程争抢导致数据不一致问题,不讨论原子性、可见性等问题。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 package com.example.socket.multithreading;
public class SafetyThread {
private static int num = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});Thread thread2 = new Thread(() -> {
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});
// 存在线程安全问题
thread1.start();
thread2.start();
Thread thread3 = new Thread(() -> {
int num = 0;
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});Thread thread4 = new Thread(() -> {
int num = 10;
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});
// 不存在线程安全问题
thread3.start();
thread4.start();
}
}
synchronized是什么?
synchronized是Java当中的一种同步锁,使用synchronized锁住的代码块都是同步方法
如何使用synchronized解决线程安全问题
一下代码均取自初识Java多线程之创建多线程的几种方式(一)中的代码只修改了其中的run方法。
用synchronized解决实现Runnable接口的线程安全问题
锁住整个run方法
1
2
3
4
5
6
7
8
9
10
11
12
public synchronized void run() {
while (num > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
num--;
}
}锁住this
1
2
3
4
5
6
7
8
9
public void run() {
synchronized (this) {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
num--;
}
}
}锁住一个对象
1
2
3
4
5
6
7
8
9
10
11private Object obj = new Object();
public void run() {
synchronized (obj) {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
num--;
}
}
}锁住当前类
1
2
3
4
5
6
7
8
9
public void run() {
synchronized (RunnableImpl.class) {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
num--;
}
}
}
用synchronized解决继承Thread类的线程安全问题
锁住一个静态方法
注:必须将synchronized加在静态方法上,防止run或者非静态方法上都是无效的,因为此时的锁不是同一个对象1
2
3
4
5
6
7
8
9
10
11
public void run() {
show();
}
public synchronized static void show() {
while (num > 0) {
System.out.println(currentThread().getName() + "当前票号:" + num);
num--;
}
}锁静态对象
注:该对象必须是静态的,原因同11
2
3
4
5
6
7
8
9
10
11private static Object object = new Object();
public void run() {
synchronized (object) {
while (num > 0) {
System.out.println(currentThread().getName() + "当前票号:" + num);
num--;
}
}
}锁住当前类
1
2
3
4
5
6
7
8
9
public void run() {
synchronized (ExtendThread.class) {
while (num > 0) {
System.out.println(currentThread().getName() + "当前票号:" + num);
num--;
}
}
}
对于继承Thread的线程不能通过锁this来保证线程安全,因为此时的this不是同一对象。
synchronized解惑
synchronized锁对象的要求是什么?
synchronized锁住的对象,对于多个线程来说必须是同一对象,换言之,多个线程必须使用同一把锁。若多个线程使用不同的锁,将不能保证线程安全问题
将synchronized加在方法上时锁住的到底是什么?
synchronized加在普通方法上时,锁住的是当前的this。加在静态方法上时锁住的是当前类
synchronized缺陷
synchronized会导致所有方法都是同步执行的对效率有一定影响,所以要控制好锁力度,能锁部分方法体就锁部分方法体,不要锁住整个方法。对于不会发生线程安全的代码块不要随意加锁。