前面我们介绍了很多关于多线程的内容,在多线程中有一个很重要的课题需要我们攻克,那就是线程安全问题。线程安全问题指的是在多线程中,各线程之间因为同时操作所产生的数据污染或其他非预期的程序运行结果。
线程安全
1)非线程安全事例
比如 A 和 B 同时给 C 转账的问题,假设 C 原本余额有 100 元,A 给 C 转账 100 元,正在转的途中,此时 B 也给 C 转了 100 元,这个时候 A 先给 C 转账成功,余额变成了 200 元,但 B 事先查询 C 的余额是 100 元,转账成功之后也是 200 元。当 A 和 B 都给 C 转账完成之后,余额还是 200 元,而非预期的 300 元,这就是典型的线程安全的问题。
enter image description here
2)非线程安全代码示例
上面的内容没看明白没关系,下面来看非线程安全的具体代码:class ThreadSafeTest {
static int number = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> addNumber());
Thread thread2 = new Thread(() -> addNumber());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("number:" + number);
}
public static void addNumber() {
for (int i = 0; i < 10000; i++) {
++number;
}
}
}
以上程序执行结果如下:number:12085
每次执行的结果可能略有差异,不过几乎不会等于(正确的)累计之和 20000。
3)线程安全的解决方案
线程安全的解决方案有以下几个维度:数据不共享,单线程可见,比如 ThreadLocal 就是单线程可见的;
使用线程安全类,比如 StringBuffer 和 JUC(java.util.concurrent)下的安全类(后面文章会专门介绍);
使用同步代码或者锁。
线程同步和锁
1)synchronized
① synchronized 介绍
synchronized 是 Java 提供的同步机制,当一个线程正在操作同步代码块(synchronized 修饰的代码)时,其他线程只能阻塞等待原有线程执行完再执行。
② synchronized 使用
synchronized 可以修饰代码块或者方法,示例代码如下:// 修饰代码块
synchronized (this) {
// do something
}
// 修饰方法
synchronized void method() {
// do something
}
使用 synchronized 完善本文开头的非线程安全的代码。
方法一:使用 synchronized 修饰代码块,代码如下: