缓存导致的可见性问题
一个线程对共享变量的修改,另外一个线程能够立刻看到,称为可见性
在多核下,多个线程同时修改一个共享变量时,如++操作,每个线程操作的CPU缓存写入内存的时机是不确定的。除非你调用CPU相关指令强刷。
线程切换带来的原子性问题
我们把一个或者多个操作在CPU执行的过程中不被中断的特性称为原子性。
高级语言里一条语句往往需要多条CPU指令完成。例如count += 1,至少需要三条CPU指令:
- 指令1:首先,需要把变量count从内存加载到CPU的寄存器;
- 指令2:之后,在寄存器中执行+1操作;
- 指令3:最后,将结果写入内存(缓存机制导致可能写入的是CPU缓存而不是内存)。
操作系统做任务切换,可以发生在任何一条CPU指令执行完,而不是高级语言里的一条语句。
编译优化带来的有序性问题
顾名思义,有序性指的是程序按照代码的先后顺序执行。
public class Singleton { static Singleton instance; static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; }}
在new操作上,我们以为 的new操作应该是:
- 分配一块内存M;
- 在内存M上初始化Singleton对象;
- 然后M的地址赋值给instance变量。
但是实际上优化后的执行路径却是这样的:
- 分配一块内存M;
- 将M的地址赋值给instance变量;
- 最后在内存M上初始化Singleton对象。
优化后会导致什么问题呢?我们假设线程A先执行getInstance()方法,当执行完指令2时恰好发生了线程切换,切换到了线程B 上;如果此时线程B也执行getInstance()方法,那么线程B在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的instance是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。