什么情况下会发生线程安全问题?什么样的代码会设计线程安全问题?
多线程实现方法:继承(extends)Thread类 ;实现(implements)Runnable接口;
根本原因:多线程的抢占式执行和随机性
假设如果程序代码都是固定单线程取执行,代码的顺序固定,程序的结果就是固定的。因为单线程就只有一条路可以走。
一个进程有多个线程组成,而在多线程的情况下,程序要抢占cpu的核心去执行代码,代码的执行顺序就会有变化。因为不同时间、不同的cpu内执行代码,就会产生无数种不同的情况,就需要保证者无数种线程去按照一定的顺序进行运行,来保证结果的正确性。只要这些结果有一种时不正确的,就会产生bug,也就是线程不安全。
例子:
class Counter{
public int count;
public void add(){
count++;
}
}
public class ThreadTest {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(()->{
//t1线程让count加5000次
for (int i = 0; i < 5000; i++) {
counter.add();
}
});
//t2线程让count加5000次
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter.add();
}
});
t1.start();
t2.start();
//main主线程等待t1,和t2的结束
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("count的值是" + counter.count);
}
}
显然答案并不是1_0000,而且两次的答案都不是一样的,程序并不符合需求,出现了bug,这也就是典型的线程安全问题。
分析:
count++这个操作,包括三个步骤:
1、首先把内存中的值,读取到CPU的寄存器中(load)
2、把CPU寄存器里的数值进行+1运算
3、把得到的结果存在内存中
如果是两个线程并发去执行这count++这个操作,就相当于两组load、add、save进行此时不同的线程调度顺序,就可能产生一些结果上的差异
画出时间、t1、t2和内存和寄存器分析:🧐
其实这个和事务的脏读问题是相似的,读到未提交(read uncommitted)是一样的。相当于t2读到的是一个t1还没提交的脏数据
怎么解决?请听下回分析