双重锁单例的不安全性

2019-04-26  本文已影响0人  Wi1ls努力努力再努力
public class DoubleCheckedLocking{
  private static Instance instance;
  
  public static Instance getInstance(){
    if( instance == null ){
      synchronized (DoubleCheckedLocking.class){
        if( instance == null){
          instance = new Instance();
        }
      }
    }  
  return instance;
  }
}

这种双重锁单例应该是已经很完美,但是关键在于 new Instance()这句代码,在底层分为 allocation;init;return;即内存分配,初始化,返回内存地址。而在底层会进行重排,重排为内存分配,返回内存地址,初始化(这种重排不违反 intra-thread semantices,能保证的是在同一线程内,初始化 happens-before 使用)。这样会导致 A线程进入后,内存分配,返回内存地址后,在初始化前,B 线程进入,发现 instance 不为 null,则返回使用。此时 instance 还为进行初始化,因此导致的线程不安全。


能完美解决的单例是用用 volatile 禁止初始化和返回的重排或静态内部类单例

public class DoubleCheckedLocking{
  private volatile static Instance instance;
  
  public static Instance getInstance(){
    if( instance == null ){
      synchronized (DoubleCheckedLocking.class){
        if( instance == null){
          instance = new Instance();
        }
      }
    }  
  return instance;
  }
}

在多线程环境,volatile 会禁止初始化和返回的重排。

public class SingletonIniti {
        private SingletonIniti() {
        }
        private static class SingletonHolder {
                private static final SingletonIniti INSTANCE = newSingletonIniti();
         }
        public static SingletonIniti getInstance() {
                return SingletonHolder.INSTANCE;
        }
}

因为类的加载和类的初始化在 jvm 内部是由 jvm 保证的线程安全。不允许多个线程同时加载同一个类。同时可能会重排,但是禁止非构造线程看到这种重排。即整个实例化对于非构造线程是透明的。整个实例化作为原子操作在构造线程中。

上一篇 下一篇

猜你喜欢

热点阅读