[Java基础]单例模式

4/22/2018 2:22:31 PM

定义

一个类只能构建一个对象的设计模式

synchronized&volatile实现单例模式

最简单的单例模式代码:

  1. 构造方法私有化
  2. instance是单例类的静态成员,初始值是null(懒汉模式)或者new Singleton(饿汉模式,不需要判断空操作,每次调用就new一个)
  3. getInstance静态方法获取单例对象
public class Singleton {
    private Singleton() {}  //私有构造函数
    private static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

上面代码存在的问题:非线程安全
当线程A和B同时进入到if (instance == null)的判断,会同时为true,就会新建2个对象
解决方法:加入同步锁
改造成:

  1. 在new操作之前加上Synchronized 同步锁,锁住整个类
  2. 进入Synchronized 临界区以后,还要再做一次判空。
    A/B两个线程同时访问的时候,线程A还没构建完对象(此时instance == null),线程B通过了第一个判空验证,不做第二次判空的话,线程A构建完对象(此时instance != null),线程B还是会再次构建instance对象
public class Singleton {
    private Singleton() {}  //私有构造函数
   private static Singleton instance = null;  //单例对象
   //静态工厂方法
   public static Singleton getInstance() {
        if (instance == null) {      //双重检测机制
         synchronized (Singleton.class){  //同步锁
           if (instance == null) {     //双重检测机制
             instance = new Singleton();
               }
            }
         }
        return instance;
    }
}

上面代码存在的问题:由JVM指令重排引起的非绝对线程安全
分析这一句:instance = new Singleton();
在JVM中会被编译成:
memory =allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance =memory; //3:设置instance指向刚分配的内存地址

也有可能是这样:
memory =allocate(); //1:分配对象的内存空间

instance =memory; //3:设置instance指向刚分配的内存地址

ctorInstance(memory); //2:初始化对象

问题出在第二种形式:
当线程A执行完成1和3,instance对象还未完成初始化,但已经不再指向null,如果此时B线程抢到了锁,那么此时B线程执行if(instance == null)的结果会是false,然后返回一个没有初始化完成的instance对象

解决方法:在instance对象前面增加一个修饰符volatile,阻止变量访问前后的指令重排,保证指令执行顺序

public class Singleton {
    private Singleton() {}  //私有构造函数
    private volatile static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
          if (instance == null) {      //双重检测机制
         synchronized (Singleton.class){  //同步锁
           if (instance == null) {     //双重检测机制
             instance = new Singleton();
                }
             }
          }
          return instance;
      }
}

其他实现单例模式的方法:

用静态内部类实现单例模式

把instance放入到一个内部类中

  1. 从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
  2. INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。
public class Singleton {
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

存在的问题:利用反射打破单例

使用上面这2种方式构建的单例模式,都存在可以利用反射重复构建对象的问题

第一步,获得单例类的构造器。
第二步,把构造器设置为可访问。
第三步,使用newInstance方法构造对象。
最后为了确认这两个对象是否真的是不同的对象,使用equals方法进行比较,比较结果是false。

//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1.equals(singleton2));

解决方法:使用枚举实现单例模式

枚举实现单例模式

public enum SingletonEnum {
    INSTANCE;
}

总结

单例模式实现方式:3种

  1. 双重锁检测:线程安全,懒加载,无法防止反射构建,支持可序列化要实现readResolve方法
  2. 静态内部类:线程安全,懒加载,无法防止反射构建,支持可序列化要实现readResolve方法
  3. 枚举:线程安全,不是懒加载,防止反射构建,支持可序列化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值