浅析单例模式

确保一个类只有一个实例,并提供该实例的全局访问点。

使用一个私有构造函数、一个私有静态变量及一个公有静态函数实现。

私有构造函数保证了不能通过构造函数创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

使用场景:线程池,数据库连接池,缓冲等对象,只能有一个实例,一旦产生多个实例就会出现问题。

优点:减少内存开销,避免对资源的多重占用,设置全局访问点,严格控制访问

缺点:没有接口,拓展困难

Key Words: 私有构造、线程安全、延迟初始化、序列化与反序列化安全、防止反射

在这里插入图片描述

在这里插入图片描述

1、懒汉式(线程不安全)

public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

注意到,私有静态变量uniqueInstance被延迟实例化,如果没有用到该类,就不会实例化uniqueInstance,节约资源。

多线程下不安全,如果多个线程能同时进入判断语句,且此时uniqueInstance为null,这样的话就会有多个线程执行实例化语句,导致实例化多次uniqueInstance。

以时间换空间,在多线程环境下存在风险

2、饿汉式(线程安全)

public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}

懒汉式线程不安全的原因是由于uniqueInstance被实例化多次,因此 直接实例化uniqueInstance 就不会有线程不安全的问题。————类加载的时候就已初始化

直接实例化的方式丢失了延迟实例化带来的节约资源的好处。

以空间换时间,故不存在线程安全问题

3、懒汉式(线程安全)

public class Singleton {
private static Singleton uniqueInstance;
private Singleton() { }
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

只对 getUniqueInstance() 方法加锁,那么一个时间点只能有一个线程能进入该方法,避免实例化多次uniqueInstance。

但是一个线程进入该方法后,其它试图进入该方法的线程必须等待,存在过长阻塞时间,效率低下。

4、双重校验锁实现DCL(线程安全)

public class Singleton {
//uniqueInstance使用volatile关键字修饰–禁止指令重排
private volatile static Singleton uniqueInstance;
private Singleton() { }
public static Singleton getUniqueInstance() {
//双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
//再校验一次,判断是否被实例化
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

1、为什么进行双重校验?

首先判断uniqueInstance是否被实例化,如果没被实例化才对实例化语句加锁。 但是如果只有一次判断的话,两个线程都会去执行实例化语句,虽然加了锁,但是只是执行先后的问题,还是会进行两次实例化。 因此需要双重校验:

1、第一个if语句用来避免uniqueInstance已经被实例化的情况,避免非必要加锁。

2、第二个if语句进行加锁,只能有一个线程进入,不会出现uniqueInstance == null时两个线程同时实例化的情况。

2、使用volatile关键字修饰uniqueInstance

实例化对象:uniqueInstance = new Singleton(); —分三步执行:

1、为uniqueInstance 分配内存空间 
2、初始化uniqueInstance  
3、将uniqueInstance 指向分配的内存空间

JVM具有指令重排的特性,执行顺序可能发生变化。
–2还没执行先执行了3,另一个线程执行时对象非空,出现DCL失效问题。

volatile关键字禁止指令重排,因此可以在多线程环境下正常运行。

volatile关键字的作用:https://blog.csdn.net/wang_chaochen/article/details/116592249

5、静态内部类实现

public class Singleton {
private Singleton() {
}

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。

当Singleton第一次被加载时,并不需要去加载SingletonHolder,只有当getUniqueInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getUniqueInstance()方法会导致虚拟机加载SingletonHolder类,

这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

延迟初始化+线程安全,但是存在传参的问题,因为静态内部类创建单例,外部无法传递参数进去。

类的初始化时机:

不同于DCL方法:

getUniqueInstance()方法,调用的是SingletonHolder.INSTANCE,取的是SingleTonHoler里的INSTANCE对象,

不管多少个线程去调用getUniqueInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。

当getUniqueInstance()方法被调用时,SingletonHolder才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建,然后再被getUniqueInstance()方法返回出去,这点同饿汉模式。

INSTANCE在创建过程中又是如何保证线程安全?

虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,

如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。

如果在一个类的()方法中有耗时很长的操作,就可能造成多个线程阻塞

(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),

在实际应用中,这种阻塞往往是很隐蔽的。

6、饿汉式+防序列化破坏

public class Singleton implements Serializable {
private final static Singleton singleton = new Singleton();

private Singleton(){
}

public static Singleton getInstance(){
    return singleton;
}

	//添加readsolve函数可防止序列化的破坏
private Object readResolve() throws ObjectStreamException{
    return singleton;
}

}

一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.

不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来.

这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.

防序列化破坏

/**

  • Reads and returns “ordinary” (i.e., not a String, Class,

  • ObjectStreamClass, array, or enum constant) object, or null if object’s

  • class is unresolvable (in which case a ClassNotFoundException will be

  • associated with object’s handle). Sets passHandle to object’s assigned

  • handle.
    */
    private Object readOrdinaryObject(boolean unshared) throws IOException{
    if (bin.readByte() != TC_OBJECT) {
    throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();

    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) {
    throw new InvalidClassException(“invalid class descriptor”);
    }

    Object obj;
    try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
    throw (IOException) new InvalidClassException(desc.forClass().getName(), “unable to create instance”).initCause(ex);
    }

     passHandle = handles.assign(unshared ? unsharedMarker : obj);
    

    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
    handles.markException(passHandle, resolveEx);
    }

     if (desc.isExternalizable()) {
     readExternalData((Externalizable) obj, desc);
    

    } else {
    readSerialData(obj, desc);
    }

     handles.finish(passHandle);
    

    // 这个位置调用readResolve方法
    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()){
    Object rep = desc.invokeReadResolve(obj);
    if (unshared && rep.getClass().isArray()) {
    rep = cloneArray(rep);
    }
    if (rep != obj) {
    handles.setObject(passHandle, obj = rep);
    }
    }
    return obj;
    }

7、饿汉式+防止反射破坏

public class Singleton implements Serializable {
private final static Singleton singleton = new Singleton();

//由于类加载时就有实例,在私有构造方法中抛出异常可以防止反射破坏
private Singleton(){
    if (singleton != null) {
        throw new RuntimeException("单例构造器禁止反射调用");
    }
}

public static Singleton getInstance(){
    return singleton;
}

private Object readResolve(){
    return hungrySingleton;
}

}

8、懒汉式 + 防反射攻击

由于懒汉式是延迟初始化,无法防反射攻击

double Check + 防反射攻击

public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
if(uniqueInstance != null){
throw new RuntimeException(“单例构造器禁止反射调用”);
}
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

9、枚举实现

public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}

public void setObjName(String objName) {
this.objName = objName;
}
}

public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName(“firstName”);
System.out.println(firstSingleton.getObjName());//firstName
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName(“secondName”);
System.out.println(firstSingleton.getObjName());//secondName
System.out.println(secondSingleton.getObjName());//secondName
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());//secondName
}
} catch (Exception e) {
e.printStackTrace();
}
}

枚举在Java中与普通类一样,能拥有字段和方法,枚举实例创建是线程安全的。
在任何情况下都是一个单例Singleton.INSTANCE。

可以防止反射攻击。枚举是由JVM保证只会实例化一次。
该实现在多次序列化和序列化之后,不会得到多个实例。

防反射:

反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。

防反序列化:

ObjectOutputStream 的序列化方法看下 Enum 类型的序列化内容,

顺着 writeobject方法找到writeObject0方法:

// ObjectOutputStream > writeobject0()
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
}

专门的 writeEnum方法:

private void writeEnum(Enum<?> en,
ObjectStreamClass desc,
boolean unshared) throws IOException
{
// 1. ENUM类型标志(常量):“126”
bout.writeByte(TC_ENUM);
ObjectStreamClass sdesc = desc.getSuperDesc();
// 2. 完整类名:“com.chaycao.java.EnumSingleton: static final long serialVersionUID = 0L;”
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
// 3. Enum对象的名称:“INSTANCE”
writeString(en.name(), false);
}

反序列化:

使用专用的 readEnum 方法:

private Enum<?> readEnum(boolean unshared) throws IOException {
// 1. 检查标志位
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

// 2. 检查类名是否是Enum类型
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
    throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
    handles.markException(enumHandle, resolveEx);
}

String name = readString(false);
Enum<?> result = null;
// 3. 加载类,并使用类的valueOf方法获取Enum对象
Class<?> cl = desc.forClass();
if (cl != null) {
    try {
        @SuppressWarnings("unchecked")
        Enum<?> en = Enum.valueOf((Class)cl, name);
        result = en;
    } catch (IllegalArgumentException ex) {
        throw (IOException) new InvalidObjectException(
            "enum constant " + name + " does not exist in " +
            cl).initCause(ex);
    }
    if (!unshared) {
        handles.setObject(enumHandle, result);
    }
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;

}

Enum.valueOf 方法:

public static <T extends Enum> T valueOf(Class enumType, String name) {
// name = “INSTANCE”
// 根据名称查找
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException(“Name is null”);
// 没有找到,抛出异常
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + “.” + name);
}

根据名称查找对象,再返回,所以仍会返回 EnumSingleton中的INSTANCE,不会存在反序列化的危险。

1、静态内部类部分 :

public class Singleton {
    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}

在这里插入图片描述
https://blog.csdn.net/mnb65482/article/details/80458571

2、饿汉式+防序列化破坏:

添加链接描述

3、懒汉式防反射攻击–怎么实现?

添加链接描述

4、枚举怎么实现防反射、防序列化?

添加链接描述

添加链接描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值