可见性
CPU缓存导致的可见性问题
描述
可见性指的是一个线程对共享变量的修改,另一个线程能够立刻看到。单核心情况下,所有线程操作的都是同一个CPU的缓存,一个线程对缓存的写,对另一个线程来说是可见的,但是在多核心情况下,每颗CPU都有自己的缓存,当线程分别在不同的CPU上操作时,共享变量一致性问题就出现了,这时一个线程对共享变量的操作对另一个线程而言就不具备可见性
图解
原子性
线程切换带来的原子性问题
描述
原子性指的是一个或多个操作在CPU执行的过程中不被中断的特性
案例
count += 1。执行这条语句至少需要三条CPU指令:1,将count变量从内存(或缓存)加载到CPU寄存器;2,在寄存器中执行count + 1操作;3, 将结果写入内存(或缓存)。操作系统的任务切换可能会发生在任何一条CPU指令执行完成后,因此这条语句的原子性将得无法保证
图解
有序性
编译器优化带来的有序性问题
描述
有序性指的是程序按照代码的先后顺序执行
案例
双重检测锁创建单例对象。创建过程大致分解为以下步骤:1,判断instance是否为null;2,分配一块内存M;3,在内存M上初始化单例对象;4,将M的地址赋值给instance变量。但是经过编译器优化后,指令3和4的顺序将颠倒,会先将M的地址赋值给instance变量,然后在内存M上初始化单例对象。如果当CPU指令执行完步骤3后切换为其他线程,则其他线程可能会返回未初始化的instance实例,这时访问就可能会触发空指针异常
代码
public class SingletonDemo {
static SingletonDemo instance;
static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
}
图解
补充
CPU、内存、I/O设备之间的速度差异。CPU增加缓存,以均衡与内存间的速度差异,操作系统增加了进程、线程以分时复用CPU,进而均衡CPU与I/O设备间的速度差异,编译程序优化指令执行次序,使得缓存能够得到更加合理的利用
[参考资料] Java并发编程实战