线程安全的单例模式分析

本文针对於单例模式中对象创建中的线程安全问题。主要以懒汉式,饿汉式,静态内部类,枚举类分析在调用时创建对象的线程安全问题。


1.饿汉式

类加载会导致该单实例对象被创建

    // 问题1:为什么加 final
    // 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
public final class HungrySingleton implements Serializable {
    // 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
    private HungrySingleton(){}
    // 问题4:这样初始化是否能保证单例对象创建时的线程安全?
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    // 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
    public static HungrySingleton getInstance(){
        return INSTANCE;
    }

    private Object readResolve(){
        return INSTANCE;
    }

}

问题回答:

  1. 用final修饰的类不能被扩展,也就是说不可能有子类。若被继承子类可能修改父类的内部结构会破坏单例模式
  2. 重写readResolve()并返回单例对象;readResolve() 读取序列保存的变量值,导致在反序列化的时通过字节码会重新生成实例导致单例被破坏
  3. 私有化创建方法导致外部不能够调用构造方法直接创建实例;不能够防止反射创建实例,Co
    constructor.setAccessible(true);即可访问私有构造函数
  4. 线程安全;成员变量是在类加载的时候完成初始化,类加载的阶段有JVM保证线程安全
  5. 方法可以灵活封装并且可以提供对泛型的支持(个人看法)

2.懒汉式

类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

private Singleton() { }
 private static Singleton INSTANCE = null;
 // 分析这里的线程安全, 并说明有什么缺点
 public static synchronized Singleton getInstance() {
 if( INSTANCE != null ){
 return INSTANCE;
 } 
 INSTANCE = new Singleton();
 return INSTANCE;
 }

问题:
synchronized同步代码块锁范围过大,导致调用初始化方法每次都需要加锁解锁,存在效率问题


3.枚举类

// 问题1:枚举单例是如何限制实例个数的
// 问题2:枚举单例在创建时是否有并发问题
// 问题3:枚举单例能否被反射破坏单例
// 问题4:枚举单例能否被反序列化破坏单例
// 问题5:枚举单例属于懒汉式还是饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
enum Singleton { 
 INSTANCE; 
}

问题回答:

  1. 该答案通过字节码来进行说明
public final enum com/lock/singleton/Singleton extends java/lang/Enum  {

  // compiled from: Singleton.java

  // access flags 0x4019
  public final static enum Lcom/lock/singleton/Singleton; INSTANCE

INSTANCE为枚举类中的静态成员变量肯定是单实例
2.静态成员变量在类加载器创建对象时已经由JVM创建完成,不存在并发问题
3.答案是不能的

@CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        //用于判断是否有Enum修饰
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
  1. 不能被序列化
  2. 在类加载时创建实例变量,属于饿汉式
  3. 以下是我个人一些实现
public class EnumSingleton {
    //私有化构造函数
    private EnumSingleton() {
    }
    //定义一个静态枚举类
    static enum EnumSingletonClass {
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private EnumSingleton enumSingleton;
        //私有化枚举的构造函数
        private EnumSingletonClass() {
            enumSingleton = new EnumSingleton();
        }
        public EnumSingleton getInstance() {
            return enumSingleton;
        }

    }
    //对外暴露一个获取对象的静态方法
    public static EnumSingleton getInstance() {
        return EnumSingletonClass.INSTANCE.getInstance();
    }
}

DCL( double-checking lock) 懒汉单例

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) { // t2 
 return INSTANCE;
 }
 INSTANCE = new Singleton(); 
 return INSTANCE;
 } 
 }
}

问题回答:


静态内部类懒汉单例

public class StaticInnerSingleton {

    private StaticInnerSingleton(){}

    // 问题1:属于懒汉式还是饿汉式
    private static class LazyHolder{
       static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }
    // 问题2:在创建时是否有并发问题
    public static StaticInnerSingleton getInstance(){
        return LazyHolder.INSTANCE;
    }
}

问题回答:

  • 1.懒汉式,因为类加载属于懒加载只有当使用的该类时才会创建。
  • 2.不会有并发问题,当外部调用getInstance,会触发类加载并由JVM创建实例,所以保证了线程安全问题。

以上为个人学习总结,若有不准备之处还望指正,共勉!

这世上总有太多不期而遇的温暖,支撑着我们走过每个难熬的瞬间

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章