现在考虑重排序后,两个线程发生了以下调用:
Time Thread A Thread BT1 检查到uniqueSingleton为空
T2 获取锁
T3 再次检查到uniqueSingleton为空
T4 为uniqueSingleton分配内存空间
T5 将uniqueSingleton指向内存空间
T6 检查到uniqueSingleton不为空
T7 访问uniqueSingleton(此时对象还未完成初始化)
T8 初始化uniqueSingleton
在这里添加volatile关键字主要是避免在对象未完整完成对象创建就已经被其他线程读取,造成空指针异常。
总结volatile 的主要作用是实现可见性和禁止指令重排。
线程安全需要满足可见性、有序性、原子性。
volatile 可以保证可见性和有序性,但是无法保证原子性,所以是线程不安全的。(非原子操作可能会导致数据缓存在CPU的cache中,产生数据不一致)
synchronized 关键字虽然可以保证可见性、有序性、原子性,而且用法简单,但是性能开销大。
双重检查锁模式是 volatile 的典型使用场景,双重检查锁通常用于实现单例模式或延迟赋值。
参考
Java中Volatile关键字详解
java volatile关键字解惑
为什么双重检查锁模式需要 volatile ?
Java中的双重检查锁(double checked locking)