线程安全的单例模式几种创建方式

线程安全的单例模式

1、双重检查锁定

原理:在getInstance()方法中,进行两次null检查。这样可以极大提升并发度,进而提升性能。

public class SingletonDoubleCheck {
   //TODO 注意如果不适用volatile 关键字无法保证线程安全
   private volatile static SingletonDoubleCheck sDoubleCheck;
   private SingletonDoubleCheck() {}
   public static SingletonDoubleCheck getInstance() {
      //第一次检查,不加锁
      if(sDoubleCheck == null) {
         System.out.println(Thread.currentThread()+" is null");
         synchronized(SingletonDoubleCheck.class) {
            //第二次检查,加锁情况下
            if(sDoubleCheck == null) {
                System.out.println(Thread.currentThread()+" is null");
                    //内存中分配空间  1
                    //空间初始化 2
                    //把这个空间的地址给我们的引用  3
               sDoubleCheck = new SingletonDoubleCheck();
            }
         }
      }
      return sDoubleCheck;
   }
}

比较老的方式,存在线程不安全问题,解决办法,加volatile关键字。

2、静态内部类实现单例模式(无锁方式

原理:通过一个静态内部类定义一个静态变量来持有当前类实例,在类加载时就创建好,使用时获取。

缺点:无法做到延迟创建对象,在类加载时进行创建会导致初始化时间变长。

public class LazySingleton {
   //TODO 通过private修饰,就是为了防止这个类被随意创建
   private LazySingleton(){}
   //TODO 利用虚拟机的类初始化机制创建单例
   private static class SingletonHolder {
      private static LazySingleton instance = new LazySingleton();
   }
   //TODO
   public static LazySingleton getInstance() {
      return SingletonHolder.instance;
   }
}

3、懒汉式

原理:类延迟创建,在单例类的内部由一个私有静态内部类来持有这个单例类的实例延迟占位模式还可以用在多线程下实例域的延迟赋值。

缺点:由于synchronized的存在,多线程时效率很低。

public class LazySingletonSynchronized {
   //TODO 通过private修饰,就是为了防止这个类被随意创建,只有内部自己可以创建
   private LazySingletonSynchronized(){
      System.out.println("LazySingleton is created!!");
   }
   // TODO 首先instance对象必须是private并且static,如果不是private那么instance的安全无法得到保证
   //TODO 其次因为工厂方法必须是static,因此变量instance也必须是static
   private static LazySingletonSynchronized instance = null;
   // TODO 为了防止对象被多次创建,使用synchronized关键字进行同步(缺点是并发环境下加锁,竞争激励的场合可能对性能产生一定影响)
   public static synchronized LazySingletonSynchronized getInstance() {
      if(instance == null) {
         instance = new LazySingletonSynchronized();
      }
      return instance;
   }
}

4、饿汉式

原理:在声明的时候就new这个类的实例,因为在JVM中,对类的加载和类初始化,由虚拟机保证线程安全每次要用时直接返回。

缺点:无法做到延迟创建对象,在类加载时进行创建会导致初始化时间变长。

public class SingletonSynchronized {
   //TODO 通过private修饰,就是为了防止这个类被随意创建,只有内部自己可以创建
   private SingletonSynchronized(){ }
   // TODO 首先instance对象必须是private并且static,如果不是private那么instance的安全无法得到保证
   //TODO 其次因为工厂方法必须是static,因此变量instance也必须是static
   private static SingletonSynchronized instance = new SingletonSynchronized();
   public static SingletonSynchronized getInstance() {
      return instance;
   }
}

注意:上述4种方式实现单例模式的缺点

1反序列化对象时会破环单例

反序列化对象时不会调用getXX()方法,于是绕过了确保单例的逻辑,直接生成了一个新的对象,破环了单例。

解决办法是:重写类的反序列化方法,在反序列化方法中返回单例而不是创建一个新的对象。

//反序列时直接返回当前INSTANCE
private Object readResolve() {
    return INSTANCE;
}

2在代码中通过反射机制,直接调用类的私有构造函数创建新的对象,会破环单例

解决办法是:维护一个volatile的标志变量在第一次创建实例时置为false;重写构造函数,根据标志变量决定是否允许创建。

private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
        flag = false;   //第一次创建时,改变标志
    }else{
        throw new RuntimeException("The instance  already exists !");
    }

5、使用枚举

原理:定义枚举类型,里面只维护一个实例,以此保证单例。每次取用时都从枚举中取,而不会取到其他实例。

public enum SingletonEnum {
    INSTANCE;
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
}

优点:

1)使用SingletonEnum.INSTANCE进行访问,无需再定义getInstance方法和调用该方法。

2)JVM对枚举实例的唯一性,避免了上面提到的反序列化和反射机制破环单例的情况出现:每一个枚举类型和定义的枚举变量在JVM中都是唯一的。 

原因:枚举类型在序列化时仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。

同时,编译器禁止重写枚举类型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值