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