设计模式 | 挑战单例模式(六)

本文探讨了序列化如何破坏Java中的单例模式,通过示例展示了序列化和反序列化过程如何导致多个实例的创建。为了解决这一问题,文章介绍了在单例类中添加`readResolve()`方法来确保反序列化时返回相同的实例。然而,这种方法并未从根本上解决内存开销问题,并提出了注册式单例作为更优解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


theme: channing-cyan

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

内容接着上篇文章《挑战单例模式(五)》,上篇文章讲到了反射破坏单例,这篇文章我们来讲序列化也会破坏单例。

八、序列化破坏单例

上一章说到反射会破坏单例,那是不是只有这样的方式会破坏呢?答案是否定的是,序列化可以避开正常的对象实例化构造,一样会破坏单例模式。

关于Java对象序列化机制,就是一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。序列化的对象可以通过反序列化后在内存中新建一个对象。所有需要实现Java字节序列化的对象需要 implements java.io.Serializable

在通过Java序列化破坏单例的过程中,我们将对象创建好以后,将对象序列化写入磁盘,下一次使用的时候再从磁盘中读取到序列化内容,然后再反序列化转为内存对象。反序列化的对象会重新分配内存,即重新创建,所以通过这样的方式去序列化再反序列化单例对象,就可能会实例化多个对象,破坏了单例。

我们重新一个可序列化的饱汉式单例,如下面的代码所示。

public class SerializableDestroySingleton implements Serializable { ​   private SerializableDestroySingleton(){       if(INSTANCE != null){           throw new AssertionError();       }       INSTANCE = this;   } ​   private static SerializableDestroySingleton INSTANCE = new SerializableDestroySingleton(); ​   public static SerializableDestroySingleton getInstance(){       return INSTANCE;   } }

跟之前实现的饱汉式单例最大的区别就是类implements java.io.Serializable

我们写一个测试main方法。

​   public static void main(String[] args) {       SerializableDestroySingleton instance1 = null;       SerializableDestroySingleton instance2 = SerializableDestroySingleton.getInstance();       try (FileOutputStream fos = new FileOutputStream("out.se");             ObjectOutputStream oos = new ObjectOutputStream(fos);             FileInputStream fis = new FileInputStream("out.se");             ObjectInputStream ois = new ObjectInputStream(fis)) {           oos.writeObject(instance2);           oos.flush();           instance1 = (SerializableDestroySingleton)ois.readObject();           System.out.println(instance1);           System.out.println(instance2);           System.out.println(instance1 == instance2);       } catch (IOException | ClassNotFoundException e) {           e.printStackTrace();       }   }

执行结果如下。

s1.png

从结果分析,反序列化后,是在内存上重新生成的实例对象,这样就破坏了实现单例的初衷。

那怎么解决这个问题呢?

可以在单例类中加一个私有方法,如下面的代码所示。

  private Object readResolve(){       return INSTANCE;   }

运行结果如下。

s2.png

这个是什么原理呢?如果要讲明白的话,要费不少时间,跟本文的主题有差距。简单说一下,对象还是实例化了两次,只是,通过反射技术,保证第二次获取的对象任然是第一次实例化的对象,如果对原理感兴趣的同学,可以写代码去调试一下,核心关注invokeReadResolve()方法就能明白。

所以其实上面的方法没有从根本上满足单例在程序运行期间,只实例化一个对象的需求,而且通过这样的方式,如果创建对象的很多,那么必然对内存的开销带来不小的挑战。那么什么能根本上解决这个问题?答案是注册式单例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值