以下是对这段代码的详细解释,这是一种实现 双重检查锁定(Double-Checked Locking) 的单例模式:
1. 类的定义
public class Singleton {private volatile static Singleton singleton;private Singleton() {}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
2. 代码逐步解析
2.1. private volatile static Singleton singleton;
private
:确保singleton
变量只能在Singleton
类内部访问,防止外部直接修改。static
:确保singleton
是类级别的变量,所有实例共享同一个变量。volatile
:保证多线程环境下的可见性和有序性。- 可见性:当一个线程修改了
singleton
的值,其他线程能够立即看到修改后的值。 - 有序性:防止指令重排序问题(即对象未完全初始化时,其他线程可能访问到未初始化的对象)。
- 可见性:当一个线程修改了
2.2. private Singleton() {}
- 将构造方法声明为
private
,防止外部通过new
关键字创建实例。 - 这是单例模式的核心,确保只能通过类的静态方法获取实例。
2.3. public static Singleton getSingleton()
这是获取单例实例的公共方法,包含了双重检查锁定的逻辑。
2.4. if (singleton == null)
- 第一次检查:在进入同步块之前,检查
singleton
是否为null
。- 如果
singleton
已经被初始化,则直接返回实例,避免不必要的同步开销。 - 如果
singleton
为null
,说明实例尚未创建,进入同步块。
- 如果
2.5. synchronized (Singleton.class)
- 同步块:对
Singleton.class
加锁,确保只有一个线程能够进入同步块,避免多个线程同时创建实例。 - 这是双重检查锁定的关键,确保线程安全。
2.6. if (singleton == null)
- 第二次检查:进入同步块后,再次检查
singleton
是否为null
。- 这是因为可能有多个线程在第一次检查时通过了
if (singleton == null)
,但只有一个线程能够进入同步块。 - 如果没有第二次检查,可能会导致多个线程创建多个实例。
- 这是因为可能有多个线程在第一次检查时通过了
2.7. singleton = new Singleton();
- 创建单例实例。
- 由于
volatile
的作用,确保对象初始化完成后,其他线程能够正确看到singleton
的值。
2.8. return singleton;
- 返回单例实例。
3. 为什么需要双重检查锁定?
双重检查锁定的目的是在保证线程安全的同时,尽量减少同步的开销:
- 第一次检查:避免每次调用
getSingleton()
都进入同步块,提高性能。 - 第二次检查:确保在多线程环境下,只有一个线程能够创建实例,保证线程安全。
4. 为什么需要 volatile
?
在没有 volatile
的情况下,可能会出现指令重排序问题:
singleton = new Singleton();
不是一个原子操作,实际上分为三步:- 分配内存。
- 调用构造函数初始化对象。
- 将对象引用赋值给
singleton
。
- 如果发生指令重排序,可能会导致以下情况:
- 线程 A 执行了第 1 步和第 3 步,但尚未完成第 2 步。
- 线程 B 访问到未完全初始化的对象,可能导致程序崩溃。
- 使用
volatile
可以禁止指令重排序,确保对象初始化的有序性。
5. 优点
- 线程安全:通过双重检查和同步块,确保多线程环境下的安全性。
- 性能优化:避免每次获取实例时都加锁,提升性能。
- 延迟加载:实例在第一次使用时才会被创建,节省内存。
6. 缺点
- 实现较复杂,容易出错。
- 在某些情况下,可能会增加代码的维护成本。
7. 总结
这段代码是经典的双重检查锁定单例模式的实现,适用于多线程环境下的单例创建。通过 volatile
和双重检查机制,既保证了线程安全,又优化了性能,是一种高效的单例模式实现方式。