4/22/2018 2:22:31 PM
定义
一个类只能构建一个对象的设计模式
synchronized&volatile实现单例模式
最简单的单例模式代码:
- 构造方法私有化
- instance是单例类的静态成员,初始值是null(懒汉模式)或者new Singleton(饿汉模式,不需要判断空操作,每次调用就new一个)
- 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个对象
解决方法:加入同步锁
改造成:
- 在new操作之前加上Synchronized 同步锁,锁住整个类
- 进入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放入到一个内部类中
- 从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
- 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种
- 双重锁检测:线程安全,懒加载,无法防止反射构建,支持可序列化要实现readResolve方法
- 静态内部类:线程安全,懒加载,无法防止反射构建,支持可序列化要实现readResolve方法
- 枚举:线程安全,不是懒加载,防止反射构建,支持可序列化