线程安全性问题(一)
✨个人主页: 不漫游-CSDN博客
前言
在上次讲线程状态的文章里提到了BLOCKED状态--->http://t.csdnimg.cn/T17sQ
因为线程 对于 锁 的竞争引起的堵塞。接着详细展开讲解~
什么是线程安全性问题?
话不多说,先看一段代码:
public class demo17 {public static void main(String[] args) throws InterruptedException {int[] count = {0};Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {count[0]++;}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {count[0]++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count[0]);}
}
分析代码,发现使用两个线程t1 t2 来累加 数组 count[0] 的值,在不运行的情况下, 是不是觉得结果是20000.
但实际运行结果却是这样:
等等。
每次都是随机值,很明显这就是一个bug。
原因
1.线程在操作系统中,随机调度,抢占式执行。
这是根本原因。
2.多个线程,同时修改同一个变量
3.修改操作不是原子的。
首先了解一下什么是原子性?
它指的是一个操作是不可分割的,要么完全执行,要么完全不执行。
在多线程环境中,如果一个操作是原子的,那么多个线程同时执行这个操作时,不会出现竞争条件或数据不一致的问题。
但是!!!count[0]++ 这个操作在Java中不是原子操作。它实际上包含以下三个步骤:
1.读取 count[0] 的当前值。
2.将读取的值加1。
3.将新值写回 count[0]。
如果两个线程同时执行这些步骤,可能会发生以下情况:
线程t1读取 count[0] 的值(假设已经到100)。
线程t2也读取 count[0] 的值(仍然是100)。
线程t1将读取的值加1(得到101)并写回。
线程t2也将读取的值加1(得到101)并写回。
结果是 count[0] 的值只增加了1,而不是2,因为两个线程都基于相同的初始值进行了递增操作。
这还是只是一种情况,而且因为原因1中的随机调度和抢占式执行。两个线程的执行顺序几乎是随机的 。也就导致了每次执行的结果都是不一样的。
这也就导致了线程安全问题的出现。
解决办法
最主要的方法就是:
就是本期的主角---锁。
简单来讲就是,通过加锁的这种方式,使一个线程在执行 count[0]++ 的操作时,其他线程的 count[0]++ 不能插队进来。如图~
看着是不是很像等待 join(),但效率是快多了~
synchronized 关键字
这个关键字就代表上锁,基本用法是:
synchronized(//需要一个锁对象进行后续的判定){
//需要打包到一起的代码
}
重新加上这个关键字:
public class demo17 {private static final Object lock = new Object(); //定义 lock 对象public static void main(String[] args) throws InterruptedException {int[] count = {0};Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {synchronized (lock) {count[0]++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {synchronized (lock) {count[0]++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count[0]);}
}
加上后,就能正常执行了~
注意
1.锁对象的作用:上面写的后续的判定就是指 是否是对 “同一个对象” 加锁.
记住,锁对象是什么不重要,重要的是多个线程的锁对象是否是同一个。
因为不是同一对象,各自加锁后也互不影响。也就不会出现阻塞或锁竞争。
比如下图~
public class demo17 {private static final Object lock1 = new Object(); //定义 lock1 对象private static final Object lock2 = new Object(); //定义 lock2 对象public static void main(String[] args) throws InterruptedException {int[] count1 = {0};int[] count2 = {0};Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {synchronized (lock1) {count1[0]++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {synchronized (lock2) {count2[0]++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count1[0]);System.out.println(count2[0]);}
}
2.锁对象的类型不能是int double 这种内置的类型,只要是Object类的子类就可以~
看下图~
3.当 synchronized 关键字修饰方法时,这意味着当一个线程正在执行该方法时,其他线程必须等待,直到当前线程执行完毕。
看到最后,如果觉得文章写得还不错,希望可以给我点个小小的赞,您的支持是我更新的最大动力