大家好,我是 codeyang~ 公众号【codeyang】记得点赞关注 😁
今天我们继续学习下单例模式,上一篇介绍了单例模式的常见创建方式以及线程安全的问题。想必大家在开发中碰见需要创建单例对象时肯定会考虑同步方法、双重检查锁定以及枚举等方式。
如果你恰巧没有使用枚举方式,那么下面介绍的两种方式就会破坏单例,创建多个对象!
打破唯一实例
序列化和反序列化
问题复现:我们将单例对象序列化到磁盘,之后从磁盘读取反序列化成对象,多次读取看是否是同一个对象?
静态内部类的方式获取单例
package com.yang.pattern.singleton.case08;
import java.io.Serializable;
/**
* @Description: 静态内部类实现单例
* @Author : 公众号codeyang
* @Date : Created 2023/5/29 10:15 上午
*/
public class Singleton implements Serializable {
private Singleton() {
}
private static class singletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance () {
return singletonHolder.INSTANCE;
}
}
序列化反序列化破坏单例
package com.yang.pattern.singleton.case08;
import java.io.*;
/**
* @Description: 序列化与反序列化
* @Author : 公众号codeyang
* @Date : Created 2023/5/29 10:22 上午
*/
public class BreakModeOne {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//writeObjectToFile();
Singleton singleton1 = readObjectFromFile();
Singleton singleton2 = readObjectFromFile();
System.out.println(singleton1 == singleton2);
}
/**
* 将单例对象写入磁盘 /Users/yang/Desktop/test.txt (Windows下更换自己的路径)
*/
public static void writeObjectToFile() throws IOException {
//获取单例对象
Singleton instance = Singleton.getInstance();
//创建输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/yang/Desktop/test.txt"));
//将对象写入到文件中
oos.writeObject(instance);
oos.close();
}
/**
* 从文件反序列化得到对象
*/
public static Singleton readObjectFromFile() throws IOException, ClassNotFoundException {
//创建输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/yang/Desktop/test.txt"));
//读取对象
Singleton instance = (Singleton) ois.readObject();
return instance;
}
}
结果
test.txt 文件为二进制文件,不需要打开。由于编码的问题也无法查看,不用关心里面的具体内容。
通过读取序列化后的 text.txt 反序列化后获取实例对象,比较两次结果为 false,说明序列化反序列化破坏了单例
反射
通过反射方式获取 Singleton 类的实例,验证其是否为单例
反射方式破坏单例
package com.yang.pattern.singleton.case08;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @Description: 反射方式破坏单例
* @Author : 公众号codeyang
* @Date : Created 2023/5/29 11:01 上午
*/
public class BreakModeTwo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Singleton类的字节码对象
Class clazz = Singleton.class;
//获取私有无餐构造对象
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建singleton对象
Singleton instance1 = (Singleton) constructor.newInstance();
Singleton instance2 = (Singleton) constructor.newInstance();
System.out.println(instance1 == instance2);
}
}
结果
反射方式获取的实例对象比较结果 false,同样证明反射也会破坏单例
枚举类方式不会出现序列化反序列化、反射单例被破坏的问题
解决单例被破坏问题
反序列化破坏单例问题解决
序列化和反序列化破坏单例问题可以通过在 Singleton 类中添加 readResolve (),确保在反序列化时返回同一个实例,从而维护单例的唯一性
package com.yang.pattern.singleton.case08;
import java.io.Serializable;
/**
* @Description: 静态内部类实现单例
* @Author : 公众号codeyang
* @Date : Created 2023/5/29 10:15 上午
*/
public class Singleton implements Serializable {
private Singleton() {
}
private static class singletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance () {
return singletonHolder.INSTANCE;
}
//解决序列化反序列化破坏单例问题
private Object readResolve () {
return singletonHolder.INSTANCE;
}
}
结果
加了 readResolve ( )就能解决问题的原因,是由于当对象被反序列化时,Java 虚拟机会检查类中是否存在 readResolve ( )方法,并在反序列化过程中调用它。通过在 readResolve ( )方法中返回实例,避免每次新生成一个实例,保证了单例的唯一性。
简单看下源码
//源码 分析
//debug进readObject
private final Object readObject(Class<?> type)
throws IOException, ClassNotFoundException
{
...
try {
//debug 进readObject0查看
Object obj = readObject0(type, false);
...
}
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
...
try {
switch (tc) {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
...
Object obj;
try {
// desc.isInstantiable()判断返回true,通过反射调用 newInstance()创建新的实例
obj = desc.isInstantiable() ? desc.newInstance() : null;
}
...
if (obj != null &&
handles.lookupException(passHandle) == null &&
//判断Singleton类中是否添加了readRsolve () 方法
desc.hasReadResolveMethod())
{
// 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
// 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
Object rep = desc.invokeReadResolve(obj);
}
通过源码证明了,在 Singleton 类中添加 readResolve ( )方法返回实例可以解决反序列化破坏单例问题。
反射破坏单例的问题解
反射在获取了构造器对象,并绕过了私有方法限制可以创建多个实例。但是我们可以在构造器中添加逻辑判断,当检查到已经存在实例时,就抛出异常或返回现有实例。这样即使是通过反射创建实例,也能保证实例的唯一性。
package com.yang.pattern.singleton.case08;
import java.io.Serializable;
/**
* @Description: 静态内部类实现单例
* @Author : 公众号codeyang
* @Date : Created 2023/5/29 10:15 上午
*/
public class Singleton implements Serializable {
private Singleton() {
//添加判断,存在实例抛出异常保证实例唯一
if (singletonHolder.INSTANCE != null){
throw new RuntimeException("实例已经被创建!");
}
}
private static class singletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance () {
return singletonHolder.INSTANCE;
}
}
结果
由此可见通过枚举方式实现单例模式,真是 yyds
JDK 中的 Runtime 类就是使用的单例设计模式,而且还是饿汉式实现的
感谢阅读 😁,这次就分享到这里,记得点赞、收藏加关注~
往期推荐