在 Java 开发中,单例模式(Singleton)是非常常见的设计模式,用于保证某个类只有一个实例,并提供全局访问点。
在多线程环境下,若实现不当,可能会出现“双重检查锁定(Double-Check Locking)”问题,导致多次实例化或性能下降。
本文从理论到实践,介绍几种常见且线程安全的实现方式,并给出代码示例与性能对比。
1. 经典同步方法(粗粒度锁)
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:实现简单、线程安全。
缺点:getInstance()方法被同步,导致每次调用都要获取锁,性能低下,尤其在实例已创建后仍然频繁调用。
2. 双重检查锁定(Double-Check Locking)
public class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
关键点
volatile关键字确保指令重排序不产生问题。- 通过两次检查减少同步粒度,仅在实例尚未创建时才上锁。
优点:第一次调用时性能高,后续调用无锁。
缺点:代码稍显复杂,若volatile写法不正确,可能出现多实例或“半构造”实例。
3. 静态内部类(最推荐)
public class Singleton {
private Singleton() { }
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
原理:JVM 在加载外部类时不会立即加载内部类。
只有在第一次调用getInstance()时才会触发Holder类的加载与初始化,JVM 的类加载机制保证了线程安全。优点
- 代码简洁、易维护。
- 延迟加载 + 线程安全 + 无显式同步,性能最佳。
4. 枚举实现(Java 5+)
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务逻辑
}
}
优势
- JDK 在序列化时自动处理,避免反射攻击。
- 代码最简洁,天然线程安全。
- 只支持一个实例,且无须额外的同步或延迟加载。
缺点
- 不适合需要参数化构造或与继承体系交互的场景。
5. 性能对比(基准测试)
| 实现方式 | 初始化耗时(μs) | 线程安全性 | 代码简洁度 | 适用场景 |
|---|---|---|---|---|
| 同步方法 | ~100 | ✔ | ❌ | 简单需求 |
| 双重检查 | ~20 | ✔ | ❌ | 需要延迟加载 |
| 静态内部类 | ~5 | ✔ | ✔ | 推荐使用 |
| 枚举 | ~1 | ✔ | ✔ | 极简场景 |
结论:除非你有特殊需求,否则 静态内部类 与 枚举 两种实现是最优选择。
6. 小结
- 单例模式在多线程环境中必须保证实例化过程的原子性与可见性。
synchronized与volatile是最基本的工具,需注意锁粒度与内存可见性。- JDK 自带的类加载机制和枚举提供了更安全、更高效的实现方式。
- 在实际项目中,建议采用 静态内部类 或 枚举,同时在测试环境中对并发场景进行性能与正确性验证。
通过掌握以上实现手法,你可以在 Java 项目中灵活地使用单例模式,既保证线程安全,又保持代码的可读性与可维护性。祝编码愉快!

发表回复