首页 > 编程语言 >并发编程的场景中的三个bug源头:可见性、原子性、有序性

并发编程的场景中的三个bug源头:可见性、原子性、有序性

时间:2023-01-05 11:23:40浏览次数:46  
标签:Singleton 编程 cpu instance 线程 内存 寄存器 有序性 bug

1.可见性:多核系统每个cpu自带高速缓存,彼此间不交换信息

case:两个线程对同一份实例变量count累加,结果可能不等于累加之和,因为线程将内存值载入各自的缓存中,之后的累加操作基于缓存值进行,并不是累加一次往内存回写一次

2.原子性:cpu分时操作导致线程的切换

case:AB两个线程同时进行count+=1,至少需要三条 CPU 指令。

指令 1:首先,需要把变量 count 从内存加载到 CPU 的寄存器;

指令 2:之后,在寄存器中执行 +1 操作;

指令 3:最后,将结果写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)。

线程A对其进行了①②操作后,切换到B线程,B线程进行了①②③,这时内存值是1,然后再切到A执行③操作,这时的值也还是1,PS:这貌似也存在可见性的问题

3.有序性:指令的重排序

case:单列模式的双重检测,代码如下

public class Singleton {
static Singleton instance;
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}

我们以为的 new 操作应该是:

①分配一块内存 M;

②.在内存 M 上初始化 Singleton 对象;

③.然后 M 的地址赋值给 instance 变量。
但是实际上优化后的执行路径却是这样的:

①.分配一块内存 M;

②.将 M 的地址赋值给 instance 变量;

③.最后在内存 M 上初始化 Singleton 对象。

也就是可能会发生①③②的重排序(编译器优化),优化后会导致什么问题呢?我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断(线程B刚要进入第一个判空条件时,发现条件不成立,直接返回instance引用,不用去获取锁。线程在synchronized块中,发生线程切换,锁是不会释放的 ,所以线程B如果来到获取锁的地方也是拿不到锁的,可以对instance进行volatile语义声明,就可以禁止指令重排序,避免该情况发生。)时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

 

4.其他问题:

4.1CPU缓存刷新到内存的时机:

cpu将缓存写入内存的时机是不确定的。除非你调用cpu相关指令强刷

4.2IO操作

io操作不占用cpu,读文件,是设备驱动干的事,cpu只管发命令。发完命令,就可以干别的事情了。

4.3寄存器切换

 寄存器是共用的,A线程切换到B线程的时候,寄存器会把操作A的相关内容会保存到内存里,切换回来的时候,会从内存把内容加载到寄存器。可以理解为每个线程有自己的寄存器

 

标签:Singleton,编程,cpu,instance,线程,内存,寄存器,有序性,bug
From: https://www.cnblogs.com/zzq919/p/17027005.html

相关文章