您的单例模式,真的单例吗?
单例模式,大家恐怕再熟悉不过了,其作用与实现方式有多种,这里就不啰嗦了。但是,咱们在使用这些方式实现单例模式时,程序中就真的会只有一个实例吗?
聪明的你看到这样的问话,一定猜到了答案是NO。这里笔者就不卖关子了,开门见山吧!实际上,在有些场景下,如果程序处理不当,会无情地破坏掉单例模式,导致程序中出现多个实例对象。
下面笔者介绍笔者已知的三种破坏单例模式的方式以及避免方法。
1、反射对单例模式的破坏
我们先通过一个例子,来直观感受一下
(1)案例
DCL实现的单例模式:
1 public class Singleton{ 2 private static volatile Singleton mInstance; 3 private Singleton(){} 4 public static Singleton getInstance(){ 5 if(mInstance == null){ 6 synchronized (Singleton.class) { 7 if(mInstance == null){ 8 mInstance = new Singleton(); 9 } 10 } 11 } 12 return mInstance; 13 } 14 }
测试代码:
1 public class SingletonDemo { 2 3 public static void main(String[] args){ 4 Singleton singleton = Singleton.getInstance(); 5 try { 6 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); 7 constructor.setAccessible(true); 8 Singleton reflectSingleton = constructor.newInstance(); 9 System.out.println(reflectSingleton == singleton); 10 } catch (Exception e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 }
执行结果:
false
运行结果说明,采用反射的方式另辟蹊径实例了该类,导致程序中会存在不止一个实例。
(2)解决方案
其思想就是采用一个全局变量,来标记是否已经实例化过了,如果已经实例化过了,第二次实例化的时候,抛出异常。实现代码如下:
1 public class Singleton{ 2 private static volatile Singleton mInstance; 3 private static volatile boolean mIsInstantiated = false; 4 private Singleton(){ 5 if (mIsInstantiated){ 6 throw new RuntimeException("Has been instantiated, can not do it again!"); 7 } 8 mIsInstantiated = true; 9 } 10 public static Singleton getInstance(){ 11 if(mInstance == null){ 12 synchronized (Singleton.class) { 13 if(mInstance == null){ 14 mInstance = new Singleton(); 15 } 16 } 17 } 18 return mInstance; 19 } 20 }
执行结果:
这种方式看起来比较暴力,运行时直接抛出异常。
2、clone()对单例模式的破坏
当需要实现单例的类允许clone()时,如果处理不当,也会导致程序中出现不止一个实例。
(1)案例
一个实现了Cloneable接口单例类:
1 public class Singleton implements Cloneable{ 2 private static volatile Singleton mInstance; 3 private Singleton(){ 4 } 5 public static Singleton getInstance(){ 6 if(mInstance == null){ 7 synchronized (Singleton.class) { 8 if(mInstance == null){ 9 mInstance = new Singleton(); 10 } 11 } 12 } 13 return mInstance; 14 } 15 @Override 16 protected Object clone() throws CloneNotSupportedException { 17 // TODO Auto-generated method stub 18 return super.clone(); 19 } 20 }
测试代码:
1 public class SingletonDemo { 2 3 public static void main(String[] args){ 4 try { 5 Singleton singleton = Singleton.getInstance(); 6 Singleton cloneSingleton; 7 cloneSingleton = (Singleton) Singleton.getInstance().clone(); 8 System.out.println(cloneSingleton == singleton); 9 } catch (CloneNotSupportedException e) { 10 e.printStackTrace(); 11 } 12 } 13 }
执行结果:
false
(2)解决方案:
解决思想是,重写clone()方法,调clone()时直接返回已经实例的对象
1 public class Singleton implements Cloneable{ 2 private static volatile Singleton mInstance; 3 private Singleton(){ 4 } 5 public static Singleton getInstance(){ 6 if(mInstance == null){ 7 synchronized (Singleton.class) { 8 if(mInstance == null){ 9 mInstance = new Singleton(); 10 } 11 } 12 } 13 return mInstance; 14 } 15 @Override 16 protected Object clone() throws CloneNotSupportedException { 17 return mInstance; 18 } 19 }
执行结果:
true
3、序列化对单例模式的破坏
在使用序列化/反序列化时,也会出现产生新实例对象的情况。
(1)案例
一个实现了序列化接口的单例类:
1 public class Singleton implements Serializable{ 2 private static volatile Singleton mInstance; 3 private Singleton(){ 4 } 5 public static Singleton getInstance(){ 6 if(mInstance == null){ 7 synchronized (Singleton.class) { 8 if(mInstance == null){ 9 mInstance = new Singleton(); 10 } 11 } 12 } 13 return mInstance; 14 } 15 }
测试代码:
1 public class SingletonDemo { 2 3 public static void main(String[] args){ 4 try { 5 Singleton singleton = Singleton.getInstance(); 6 FileOutputStream fos = new FileOutputStream("singleton.txt"); 7 ObjectOutputStream oos = new ObjectOutputStream(fos); 8 oos.writeObject(singleton); 9 oos.close(); 10 fos.close(); 11 12 FileInputStream fis = new FileInputStream("singleton.txt"); 13 ObjectInputStream ois = new ObjectInputStream(fis); 14 Singleton serializedSingleton = (Singleton) ois.readObject(); 15 fis.close(); 16 ois.close(); 17 System.out.println(serializedSingleton==singleton); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 22 } 23 }
运行结果:
false
(2)解决方案
在反序列化时的回调方法 readResolve()中返回单例对象。
1 public class Singleton implements Serializable{ 2 private static volatile Singleton mInstance; 3 private Singleton(){ 4 } 5 public static Singleton getInstance(){ 6 if(mInstance == null){ 7 synchronized (Singleton.class) { 8 if(mInstance == null){ 9 mInstance = new Singleton(); 10 } 11 } 12 } 13 return mInstance; 14 } 15 16 protected Object readResolve() throws ObjectStreamException{ 17 return mInstance; 18 } 19 }
结果:
true
以上就是笔者目前已知的三种可以破坏单例模式的场景以及对应的解决办法,读者如果知道还有其他的场景,记得一定要分享出来噢,正所谓“独乐乐不如众乐乐”!!!
单例模式看起来是设计模式中最简单的一个,但“麻雀虽小,五脏俱全”,其中有很多细节都是值得深究的。即便是本篇介绍的这几个场景,也只是介绍了一些梗概而已,很多细节还需要读者自己去试验和推敲的,比如:通过枚举方式实现单例模式,就不存在上述问题,而其它的实现方式似乎都存在上述问题!
后记
本篇参(剽)考(窃)了如下资料:
高洪岩的《Java 多线程编程核心技术》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
2017-06-15 模板模式之clone()方法