多线程下单例模式的问题

  • 多线程情况懒加载的单例模式,会导致多个线程同时加载对象
  • 此时可以采用二次检测的机制,实现对于性能以及安全的保障
  • 使用volatile关键字,可以保证可见性以及避免了各种优化造成的空指针问题,防止初始化实例写数据还没有完成,造成此时读数据的不准去

解决一:二次检测解决

public class SingletonObject2 {
    private static volatile SingletonObject2 instance ;

    private SingletonObject2(){
        //empty
    }

    public static SingletonObject2 getInstance(){
        if (null == instance){//两次检测的方式实现多线程情况下的懒加载
            synchronized (SingletonObject2.class){
                if (null == instance) {
                    instance = new SingletonObject2();
                }
            }
        }
        return SingletonObject2.instance;
    }
}

注意:<mark>如果不加volatile关键字,可能会出现空指针异常</mark>

原因

  • 如果实例化SingletonObject2时,在构造方法中可能需要构造其他的实例,如Obj1,Obj2
  • JVM创建的顺序可能是:
  • Obj1 -> Obj2 -> SingletonObject2

  • 但是如果不加volatile关键字,可能会出现重排序,出现:
  • SingletonObject2->Obj1 ->Obj2

解决二:优雅的方式:Holder

  • 利用static类,将加载实例的方式放在类的属性中
  • 类加载器在没有使用到该类的时候,类里面的属性并没有被加载,保证了懒加载
  • static的特性可以保证JVM创建该属性的时候只会存在一个,保证了单例
  • 由于没有加锁,所以性能也可以保证
public class SingletonObject2 {

    private SingletonObject2(){
        //empty
    }


    private static class InstanceHolder{
        private final static SingletonObject2 instance = new SingletonObject2();
    }


    public static SingletonObject2 getInstance(){
        return InstanceHolder.instance;
    }
}

解决三:优雅的方式:枚举类型

  • 枚举类型的构造函数只会调用一次,保证了其单例的特性
public class SingletonObject2 {

    private SingletonObject2(){
        //empty
    }


    private enum Singleton{
        INSTANCE;

        private final SingletonObject2 instance;

        Singleton(){
            instance = new SingletonObject2();
        }
        public SingletonObject2 getInstance(){
            return instance;
        }
    }

    public static SingletonObject2 getInstance(){
        return Singleton.INSTANCE.getInstance();
    }


}