单例模式(Singleton Pattern)是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这在需要控制资源(如数据库连接池、配置管理、日志对象)或保证业务唯一性(如序列号生成器)的场景中非常有用。
单例模式的核心特点
实现一个单例模式通常需要满足以下三个核心条件:
- 私有化构造方法:防止外部通过
new 关键字直接创建实例。
- 私有化静态实例引用:在类内部持有该唯一实例的引用。
- 公有化静态获取方法:提供一个全局的静态方法来获取这个唯一实例。
为什么需要特别注意实现方式?
仅满足上述三点基础的实现可能并不可靠,主要面临两个挑战:
- 反射攻击:通过
java.lang.reflect.AccessibleObject.setAccessible 方法可以绕过私有构造器的限制。
- 序列化与反序列化:一个已被序列化的单例对象,在反序列化时可能会生成新的实例,破坏单例性。
下面我们将详细解析几种常见的单例实现方式,并分析其优缺点。
懒汉式 (Lazy Initialization)
懒汉式的特点是实例在第一次被使用时才创建,延迟加载可以节约资源。
1. 基础懒汉式(线程不安全)
这是最简单的形式,但在多线程环境下会创建多个实例。

public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
问题:当多个线程同时执行到 if (instance == null) 判断时,可能都会进入条件内,从而创建多个实例。
2. 同步方法懒汉式(线程安全但低效)
通过给获取方法加 synchronized 关键字来实现线程安全。

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
缺点:虽然保证了线程安全,但每次调用 getInstance() 都需要进行同步,性能开销较大。
3. 双重检查锁定 (Double-Checked Locking, DCL)
为了兼顾线程安全和性能,双重检查锁定模式应运而生。

public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
原理:第一次检查避免不必要的同步,第二次检查在同步块内确保只有一个线程创建实例。但它仍然有一个潜在问题:instance = new Singleton() 这行代码并非原子操作,在Java内存模型中可能发生指令重排序,导致其他线程获取到一个未初始化完成的对象。
解决方案:使用 volatile 关键字修饰实例变量,禁止指令重排序。

private volatile static Singleton instance;
4. 静态内部类实现(推荐)
这是一种更优雅的懒汉式实现,利用了类加载机制来保证初始化实例时只有一个线程,且无需同步开销。

private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
优点:线程安全,延迟加载,效率高。因为 SingletonHolder 是私有的,只有在 getInstance() 方法被调用时才会加载并初始化 INSTANCE。
饿汉式 (Eager Initialization)
饿汉式的特点是类加载时就初始化实例,基于JVM的类加载机制,本身就是线程安全的。

private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
优点:实现简单,线程安全。
缺点:无论是否用到,类装载时就完成实例化,如果该实例很耗资源且从未使用,会造成内存浪费。
枚举式 (Enum Singleton) - 《Effective Java》推荐
使用枚举是实现单例模式的最佳方式之一,它能天然防御反射攻击和序列化问题。

enum SingletonEnum {
INSTANCE;
// 可以在此添加实例方法
public void doSomething() {
// ...
}
}
访问方式:SingletonEnum.INSTANCE
优势:
- 线程安全:枚举实例的创建是线程安全的。
- 防止反序列化创建新对象:JVM保证每个枚举类型及其定义的枚举常量在全局都是唯一的,在序列化和反序列化过程中不会产生新的实例。
- 防止反射攻击:JDK源码中明确禁止使用反射创建枚举实例。
总结与选择建议
| 实现方式 |
线程安全 |
延迟加载 |
防止反射/序列化破坏 |
性能 |
实现复杂度 |
| 基础懒汉式 |
❌ |
✅ |
❌ |
高 |
简单 |
| 同步方法懒汉式 |
✅ |
✅ |
❌ |
低 |
简单 |
| 双重检查锁定(DCL) |
✅ |
✅ |
❌ |
较高 |
中等 |
| 静态内部类 |
✅ |
✅ |
❌ |
高 |
简单 |
| 饿汉式 |
✅ |
❌ |
❌ |
高 |
简单 |
| 枚举 |
✅ |
❌/✅* |
✅ |
高 |
简单 |
*枚举的实例在类加载时被创建,可视为一种“饿汉式”。但其在访问时才触发类加载的特性,在某些情况下也可达到类似延迟加载的效果。
在并发编程场景下,如果需要延迟加载,静态内部类是实现单例的优选方案。如果对资源消耗不敏感,简单的饿汉式也是不错的选择。而在现代Java开发中,枚举因其简洁性和绝对的安全性,被广泛认为是实现单例模式的最佳实践。