Java单例模式七种写法:从基础到线程安全最佳实践
单例模式确保一个类在进程中仅有一个实例,提供全局访问点。本文深入剖析Java中单例模式的七种写法,包括饿汉式、懒汉式、双重校验锁等,重点讲解如何通过`synchronized`和`volatile`解决多线程下的线程安全问题,并对比各方案的优缺点与适用场景。
单例模式核心概念与利弊
单例模式(Singleton Pattern)的核心目标是确保当前进程中某个类只有一个实例对象。正如成语“天无二日”所喻,这种设计在全局范围内严格控制对象的创建。
应用场景
- 需要生成唯一序列号的环境
- 项目内需要共享访问点或数据(如配置管理器)
- 对象创建消耗资源极大(如数据库连接池)
- 工具类或静态常量定义
优势与局限
优势:内存中仅存一个实例,减少内存开销;避免对资源的多重占用;降低系统性能损耗。
局限:缺乏接口导致扩展困难;难以进行单元测试;可能与单一职责原则产生冲突。
1. 饿汉式(Hungry Singleton)
这是最简单的实现方式。类加载时便完成实例化,不存在延迟加载。
public class Singleton_1 {
// 类加载即初始化,线程安全
private static final Singleton_1 instance = new Singleton_1();
private Singleton_1() {}
public static Singleton_1 getInstance() {
return instance;
}
}
特点:
- 线程安全:由于依赖JVM类加载机制保证初始化,天然线程安全。
- 缺点:无论是否使用,启动时就会占用内存,属于“非懒加载”。
2. 懒汉式(Lazy Singleton - 线程不安全)
仅在调用getInstance方法时才创建实例,实现了延迟加载。
public class Singleton_2 {
private static Singleton_2 instance;
private Singleton_2() {}
public static Singleton_2 getInstance() {
if (instance == null) {
instance = new Singleton_2();
}
return instance;
}
}
风险:此写法在多线程环境下是不安全的。

当两个线程同时进入if (instance == null)判断时,可能分别创建一个实例,导致单例失效。
3. 懒汉式(加锁版)
为了解决线程安全问题,直接在方法上添加synchronized关键字。
public synchronized static Singleton_3 getInstance() {
if (instance == null) {
instance = new Singleton_3();
}
return instance;
}
评价:虽然解决了线程安全,但每次调用getInstance都需要获取锁,导致严重的性能浪费。这并非最优解。
4. 双重校验锁(DCL)
结合延迟加载与高性能需求,推荐采用双重校验锁(Double-Checked Locking)。
public class Singleton_4 {
// volatile防止指令重排
private static volatile Singleton_4 instance;
private Singleton_4() {}
public static Singleton_4 getInstance() {
// 第一重校验:非空检查,避免不必要的同步
if (instance == null) {
// 同步块
synchronized (Singleton_4.class) {
// 第二重校验:确保只有一个线程能创建实例
if (instance == null) {
instance = new Singleton_4();
}
}
}
return instance;
}
}
关键原理分析
为什么需要双重校验?
若没有第二次if (instance == null)判断,在高并发下,第一个线程获取锁并实例化后释放锁,第二个线程进入同步块时不再检查,会再次创建新实例,破坏单例。
为什么必须使用volatile?
语句instance = new Singleton()并非原子操作,它在JVM层面分为三步:
- 分配内存空间
- 初始化对象
- 将引用指向内存地址
在没有volatile的情况下,编译器可能发生指令重排序,执行顺序变为:分配内存 → 引用指向内存 → 初始化对象。

假设线程A执行完第一步和第三步(未完成初始化),此时线程B进入。第一次校验发现instance != null,直接返回instance给线程B。线程B使用的将是一个初始化未完成的对象,导致程序崩溃。

加入volatile可禁止指令重排序,并保证内存可见性,确保只有完全初始化的对象才能被其他线程读取。
5. 静态内部类(Static Inner Class)
利用JVM类加载机制实现线程安全与懒加载的结合,是业界推荐的优雅写法。
public class Singleton_5 {
private Singleton_5() {}
private static class InnerSingleton {
private static final Singleton_5 instance = new Singleton_5();
}
public static Singleton_5 getInstance() {
return InnerSingleton.instance;
}
}
原理:
- 外部类
Singleton_5加载时,不会初始化内部类。 - 只有在调用
getInstance时,才会触发InnerSingleton的加载。 - JVM保证类加载过程的线程安全性,因此无需加锁即可实现线程安全的懒加载。
6. CAS原子操作(Atomic Reference)
基于乐观锁思想的变体实现,不使用传统的重量级锁。
import java.util.concurrent.atomic.AtomicReference;
public class Singleton_6 {
private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<>();
private Singleton_6() {}
public static Singleton_6 getInstance() {
while (true) {
Singleton_6 instance = INSTANCE.get();
if (null == instance) {
INSTANCE.compareAndSet(null, new Singleton_6());
}
return INSTANCE.get();
}
}
}
评价:这是一种较为罕见的写法。相比悲观锁(synchronized),它更轻量。但如果竞争激烈,忙等(busy-wait)会消耗大量CPU资源。
7. 枚举单例(Enum Singleton)
《Effective Java》作者Joshua Bloch极力推崇的实现方式。
public enum Singleton_7 {
INSTANCE;
public void anyMethod(){
System.out.println("do anything");
}
}
调用方式:Singleton_7.INSTANCE.anyMethod();
优势:不仅天然支持序列化,而且彻底杜绝了通过反射或反序列化破坏单例的可能,是解决一切单例问题的终极方案,尽管代码最为简练。
总结与最佳实践建议
在实际开发中:
- 无延迟加载需求:直接使用饿汉式,简单且高效。
- 有延迟加载需求:首选静态内部类法,兼顾懒加载与线程安全;或采用DCL双重校验锁(需注意volatile)。若想追求极致的防破解能力,推荐使用枚举。
面试考察重点通常在于理解DCL为何要加volatile以及指令重排的具体危害。以上就是Java单例模式七种写法的详细内容,更多关于Java设计模式实战的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













