1. 为什么会出现线程安全问题?
原因 : 线程在系统中的调度是无序的/随机的(抢占式执行)
线程不安全的原因
1.1 抢占式执行
1.2 多个线程修改同一个变量
1.3 修改操作不是原子的
举个🌰
static class Counter{
private int count = 0;
public void add(){
count++;
}
public int getCount() {
return count;
}
}
public static void main(String[] args) throws InterruptedException{
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.getCount());
}
两次运行结果都不一致
上述代码对变量 count 通过 线程 t1 和 线程 t2 进行 ++ 一万次 ,我们想要的结果是 20000,随着运行,结果却并不是.这里就是 系统 抢占式执行 造成的结果
我们来解析 ++ 这个 操作
这个时候我们要进行加锁操作
这里就是谁调用 add 谁进行加锁
还有一个线程不安全问题
内存可见性,造成线程不安全
举个🌰
private static int flag = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (flag == 0){
}
System.out.println("循环结束");
});
Thread t2 = new Thread(() -> {
Scanner scan = new Scanner(System.in) ;
System.out.println("请输入一个整数:");
flag = scan.nextInt();
});
t1.start();
t2.start();
}
上述代码,我们创建两个线程, t1 中 变量 flag 为零 陷入死循环, t2 中,我们从键盘上输入一个整数,改变 flag 跳出循环,打印 循环结束,预期结果却并不是,而是 t1 还在跑,陷入死循环,这里就是内存可见性问题,导致线程不安全
while() 循环,会执行 load(从内存中读取数据到cpu中) 和 cmp(比较寄存器里的值是否为0) 操作
编译器发现 多次 load 的值都相同,就进行优化,使用上次的值,就会导致程序出现bug
解决方法 使用 volatile 关键字
被 volatile 修饰的变量,就会阻止编译器进行优化,每次都会从内存中重新读取数据
这样就解决了,内存可见性导致的线程安全问题了
最后,volatile 关键字 还可以禁止指令重排序
画个图,给大家演示
最后,祝大家身体健康,天天开心哦~ 🌹