单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,本文尝试分析每种实现下获取单例对象(即getInstance)时的线程安全(创建或获取时),并思考注释中的问题。
饿汉式:类加载就会导致该单实例对象被创建 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建
1. 饿汉单例
// 问题1: 为什么加final// 问题2: 如果实现了序列化接口,还要做什么来防止反序列化破坏单例public final class Singleton implements Serializable {// 问题3: 为什么设置私有?是否能防止反射创建新的实例?private Singleton(){}// 问题4: 这样初始化是否能保证单例对象创建时的线程安全?private static final Singleton INSTANCE = new Singleton();// 问题5: 为什么提供静态方法而不是直接将INSTANCE设置为public, 说出你的理由public static Hunger getInstance(){return INSTANCE;}public Object readResolve(){return INSTANCE;}}
答:
- 加final是防止子类不适当的方法覆盖父类破坏单例
- 实现readResolve方法,如上文所示,返回该单例
- 设置私有防止其他类调用构造创建对象,不能防止反射,因为反射可以暴力破坏私有访问条件
- 能,静态成员变量初始化是在类加载阶段完成,由jvm保证线程安全
- 方法可以提供更好的封装,方便内部改进实现懒惰加载,可以对单例对象有更多控制,可以提供泛型支持等
2. 枚举单例
// 问题1:枚举单例是如何限制实例个数的// 问题2:枚举单例在创建时是否有并发问题// 问题3:枚举单例能否被反射破坏单例// 问题4:枚举单例能否被反序列化破坏单例// 问题5:枚举单例属于懒汉式还是饿汉式// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做enum Singleton {INSTANCE;}
答:
- 枚举定义的枚举对象定义时有几个就有几个,相当于枚举类的静态成员变量(public static final)
- 没有,其成员变量也是在类加载时创建
- 不能
- 不能,默认实现序列化接口,实现时考虑到了这个问题
- 饿汉式
-
3. 懒汉单例
public final class Singleton {private Singleton() { }private static Singleton INSTANCE = null;// 分析这里的线程安全, 并说明有什么缺点public static synchronized Singleton getInstance() {if( INSTANCE != null ){return INSTANCE;}INSTANCE = new Singleton();return INSTANCE;}}
答:
可以保证线程安全,static上的synchronized方法将锁加在了类对象上。缺点是锁的范围比较大,性能比较低4. DCL 懒汉单例(双重校验锁)
public final class Singleton {private Singleton() { }// 问题1:解释为什么要加 volatile ?private static volatile Singleton INSTANCE = null;// 问题2:对比实现3, 说出这样做的意义public static Singleton getInstance() {// 第一层校验加快速度,提高性能if (INSTANCE != null) {return INSTANCE;}synchronized (Singleton.class) {// 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗// 第二层校验避免并发创建多个对象if (INSTANCE != null) { // t2return INSTANCE;}INSTANCE = new Singleton();return INSTANCE;}}}
答:
因为synchronized方法里面构造方法的指令可能被重排,有可能导致一个线程在创建单例时,另一个线程获取了尚未初始化的单例对象。要使用volatile防止指令重排
- synchronized范围缩小了,性能更强,只在第一次创建对象时需要加锁
- 防止首次创建对象时并发的问题
也可以有一下通用形式:
public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getUniqueInstance() {if (uniqueInstance == null) {synchronized (Singleton.class) {if (uniqueInstance == null) {uniqueInstance = new Singleton();}}}return uniqueInstance;}}
5. 静态内部类懒汉单例
public final class Singleton {private Singleton() { }// 问题1:属于懒汉式还是饿汉式private static class LazyHolder {static final Singleton INSTANCE = new Singleton();}// 问题2:在创建时是否有并发问题public static Singleton getInstance() {return LazyHolder.INSTANCE;}}
答:
- 懒汉式,类加载本身就是懒惰的,静态内部类加载时只有在用到时才会发生
- 没有,类加载有jvm保证线程安全
推荐使用枚举和静态内部类懒汉单例
