C05 单例模式 懒汉式的线程安全问题及解决方案(二) 双重检查 & volatile

双重检查基础版

  • 进入getInstance()做第一次检查;
  • 进入synchronized代码块做第二次检查;
  • 这样使用synchronized将大幅降低把synchronized加在方法上的性能开销;
public class LazyDoubleCheckSingleton {
    
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    
    private LazyDoubleCheckSingleton() {}

    public static LazyDoubleCheckSingleton getInstance() {
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                    // 1.分配内存给这个对象
                    // 2.初始化对象
                    // 3.设置lazyDoubleCheckSingleton指向刚分配的内存
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
    
}

双重检查基础版中由重排序引发的问题

  • 做第一次检查的时候,如果lazyDoubleCheckSingleton != null,并不代表lazyDoubleCheckSingleton一定已经初始化完成了,造成这种情形的原因是指令重排序;
  • lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); 这句在底层经历了3个动作:
    1.分配内存给这个对象;
    2.初始化对象;
    3.设置 lazyDoubleCheckSingleton 指向刚分配的内存;
    这3个动作中,2和3的动作可能颠倒,其造成的结果就是:Thread-0第一次检查的时候,由于Thread-1先执行3,lazyDoubleCheckSingleton 指向刚分配的内存,导致Thread-0看到的 lazyDoubleCheckSingleton 不为空,直接返回 lazyDoubleCheckSingleton,但此时lazyDoubleCheckSingleton 在Thread-1中还没有初始化,所以造成程序出问题;
  • Java规范中有个 intra-thread semantics 的规定,它保证重排序不会改变单线程的执行结果,重排序可以提高程序的执行性能;

重排序问题的解决方案

  • 不允许重排序;
  • 允许重排序,但不允许另一个线程看到这个重排序;

不允许重排序的解决方案

  • 用 volatile 修饰 lazyDoubleCheckSingleton,就禁止了重排序;
  • 在多线程的时候,多CPU会共享内存,加了 volatile 后,所有的线程都能看到共享内存的最新状态,保证了内存的可见性;
  • 用 volatile 修饰的共享变量在进行写操作的时候,会将当前CPU缓存行的数据写进内存,使得其他CPU缓存了该内存地址的数据无效,从而迫使其他CPU重新从共享内存中获取数据,这样就保证了内存的可见性;
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

你可能感兴趣的:(C05 单例模式 懒汉式的线程安全问题及解决方案(二) 双重检查 & volatile)