【设计模式】并发编程下的单例模式

一、饿汉单例#

1. 静态变量实现#

Copy
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }

这种实现方式基于class loader机制避免了多线程的同步问题。不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

2. 静态代码块实现#

Copy
public class Singleton { private Singleton instance = null; private Singleton() {} static { instance = new Singleton(); } public static Singleton getInstance() { return this.instance; } }

这种实现方式表面上看起来与上面差别挺大的,其实跟上面的差别不大,都是在类初始化即实例化instance。

二、懒汉单例#

1. 基本实现,线程不安全#

Copy
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }

这种是最基本的lazy loading实现的单例模式,但是缺点很明显,在多线程下显然不能安全工作。

2. synchronized同步静态方法,线程安全#

Copy
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }

这种写法能够很好的在多线程中工作,不会出现并发安全问题,也实现了懒加载。但是效率很低,synchronized使得这里并行变成串行。所以这种写法一般不会被使用到。

3. 静态内部类实现,推荐使用#

Copy
public class Singleton { // === 静态内部类 === private static class SingletonHolder { private static final INSTANCE = new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }

这种方式同样利用了class loader 的机制来保证初始化instance时只有一个线程,它跟上面提到的饿汉单例不同的是(很细微的差别):上面的饿汉单例是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy-loading效果)。而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示调用getInstance()方法时,才会显示装载SingletonHolder类,从而实例化instance。

4. 双重校验锁实现,推荐使用#

Copy
public class Singleton { // ===1:volatile修饰 private volatile static Singleton singleton; private Singleton() {} public static Singleton getInstance() { // ===2:减少不必要同步,性能优化 if (singleton == null) { // ===3:同步,线程安全 synchronized(Singleton.class) { if (singleton == null) { // ===4:创建singleton对象 singleton = new Singleton(); } } } return singleton; } }
  • 为什么要使用volatile修饰?

    虽然已经使用synchronized进行同步,但是第四步创建对象时,会有下面的伪代码:

    Copy
    memory=allocate(); // 1. 分配内存空间 ctorInstance(); // 2. 初始化对象 singleton=memory; // 3. 设置singleton指向刚排序的内存空间

    当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以 JVM 是允许的。如果此时伪代码发生重排序,步骤为1 -> 3 -> 2,线程A执行到第3步时,线程B调用getInstance()方法,在判断singleton == null时不为null,则返回singleton。但此时singleton并没有初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!

5. 枚举实现,推荐使用#

Copy
public class Singleton { private Singleton(){} // === 延迟加载 private enum EnumHolder { INSTANCE; private static Singleton instance = null; private Singleton getInstance() { instance = new Singleton(); return instance; } } public static Singleton getInstance() { return EnumHolder.INSTANCE.instance; } }

三、问题注意#

1. 不同类加载器加载#

如果单例由不同的类加载器加载,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些Servlet容器对每个Servlet使用完全不同的类加载器,这样的话如果有两个Servlet访问一个单例类,它们就都会有各自的实例。

可以用下面方式对这个问题进行修复:

Copy
private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null) { classLoader = Singleton.class.getClassLoader(); } return (classLoader.loadClass(classname)); }

2. 序列化问题#

如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原,不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

Copy
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() {} private Object readResolve() { return INSTANCE; } }
posted @   周二鸭  阅读(250)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示
CONTENTS