文章目录
前言
本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!
本篇文章主要讲解了线程不安全的场景,以及如何解决线程不安全问题,内容可能有点抽象,希望大家可以慢慢咀嚼,好好吸收。
一、线程不安全场景
何为线程安全
- 如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的
导致线程不安全的原因,主要是有三个场景。
1️⃣ 多个线程对同一个共享数据进行修改操作
2️⃣ 内存可见性(是编译器出现了误判后,对代码做出的错误优化)
3️⃣ 指令重排序(是编译器出现了误判后,对代码做出的错误优化)
编译器优化
编译器优化:智能的调整你的代码执行逻辑,保证程序结果不变的前提下,通过加减语句,通过语句执行顺序变换,通过一些操作,让整个程序执行的效率大大提升。
编译器对于"程序结果不变"
- 单线程下判定是非常准确的。
- 多线程下判定就不一定了,可能调整后,效率提高了,但是结果改变了(编译器出现误判),引起程序出现bug。
1. 多个线程修改同一共享数据
public class ThreadDemo2 {
private static int i = 0;//全局变量
public static void main(String[] args) throws InterruptedException {
// 创建一个线程让i++ 5000次
Thread thread1 = new Thread(() -> {
for(int j = 0;j < 5000;j++) {
i++;
}
});
// 再创建一个线程让i++ 5000次
Thread thread2 = new Thread(() -> {
for(int j = 0;j < 5000;j++) {
i++;
}
});
thread1.start();
thread2.start();
// 两个线程开始执行
thread1.join();
thread2.join();
// 等待两个线程执行完毕打印i
System.out.println(i);
}
}
上述代码,运行了三次,三次结果都不一样且都不符合预期结果的10000。
那么为什么会造成以上结果呢??
造成以上线程不安全问题主要有三个原因
1️⃣ 线程之间是抢占式执行的,CPU执行到任意一条语句都可能被调度去执行其他线程 (罪魁祸首,主要原因)
2️⃣ 多个线程修改同一个共享变量
3️⃣ 修改操作不是原子性的
前两条是代码的执行机制,而这两个机制,遇上第三个原因,就会出现大问题。
什么是原子性?
在以前,人们还没有发现中子,质子,电子时,人们认为不可分割的最小物质就是原子。
因此,我在执行一个操作时,
如果这个操作不可以被分割成几个步骤,必须一次性执行完毕,那么这个操作具备原子性。
如果这个操作可以被分割成几个步骤,可以通过CPU调度来间断性的完成这个操作,那么这个操作不具备原子性。
某个操作对应单个CPU指令,那么这个操作就是原子性的
某个操作对应多个CPU指令,那么这个操作大概率就不是原子性的
使用 ‘=’ 赋值,就是一个原子性操作。
而i++对应了三个CPU指令。
就比如上述的i++操作,是由三步操作组成的。
1️⃣load(从内存把数据读到CPU)
2️⃣add(CPU对数据进行运算)
3️⃣save(把数据写回内存)
此时已经执行了两次i++,而 i 仍等于1。
此处i++这个操作是由三个CPU指令来完成的,因此两个线程,抢占式执行,就可能存在多种指令顺序排列,因此造成bug。