本文针对于单例模式中对象创建中的线程安全问题。主要以懒汉式,饿汉式,静态内部类,枚举类分析在调用时创建对象的线程安全问题。
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;
}
}
问题回答:
- 用final修饰的类不能被扩展,也就是说不可能有子类。若被继承子类可能修改父类的内部结构会破坏单例模式
- 重写readResolve()并返回单例对象;readResolve() 读取序列保存的变量值,导致在反序列化的时通过字节码会重新生成实例导致单例被破坏
- 私有化创建方法导致外部不能够调用构造方法直接创建实例;不能够防止反射创建实例,Co
constructor.setAccessible(true);即可访问私有构造函数 - 线程安全;成员变量是在类加载的时候完成初始化,类加载的阶段有JVM保证线程安全
- 方法可以灵活封装并且可以提供对泛型的支持(个人看法)
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;
}
问题回答:
- 该答案通过字节码来进行说明
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;
}
- 不能被序列化
- 在类加载时创建实例变量,属于饿汉式
- 以下是我个人一些实现
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创建实例,所以保证了线程安全问题。
以上为个人学习总结,若有不准备之处还望指正,共勉!