确保线程安全的延迟初始化是多线程编程中的一个关键问题。在Java中,存在几种模式可以实现线程安全的延迟初始化,我们将深入探讨其中的几种方法,包括同步方法(Synchronized Access)、双重检查锁定(Double-Checked Locking)以及使用Initialization-on-demand holder
模式。
1. 同步方法(Synchronized Access)
最简单的线程安全延迟初始化方法是将获取实例的方法同步。这种方法确保只有一个线程可以执行初始化代码路径。
public class Singleton {
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton() {}
}
这种方法的缺点是每次访问getInstance()
方法时都需要进行同步,这可能会严重降低性能。
2. 双重检查锁定(Double-Checked Locking)
为了避免同步方法的性能问题,可以使用双重检查锁定模式。这种方法首先检查实例是否已经创建,如果尚未创建,才进入同步块。
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() {}
}
这里使用volatile
关键字是必要的,以防止JVM的指令重排序优化。这种优化可能会导致在对象构造完成前就将地址赋给instance
字段,从而导致并发环境下的错误。
3. Initialization-on-demand holder idiom
使用Initialization-on-demand holder idiom
是一种既实现延迟初始化又保证了线程安全的优雅方法,它依赖于Java语言规范保证的类初始化阶段的安全性。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
在这种模式下,SingletonHolder
类将延迟加载,直到getInstance()
方法第一次被调用时,JVM才加载并初始化SingletonHolder
类。这时,Singleton
实例被创建。JVM确保了INSTANCE
的唯一性和线程安全性。
比较
- 使用同步方法是最简单直接的线程安全延迟初始化方法,但它在高并发场景下性能较差。
- 双重检查锁定提高了性能,但实现更复杂,并且需要正确使用
volatile
关键字以避免指令重排序问题。 - Initialization-on-demand holder idiom提供了一种既高效又线程安全的延迟初始化方法,是实现单例模式的推荐方式。
在实际应用中,选择哪种方法取决于具体的需求和环境。对于大多数情况,推荐使用Initialization-on-demand holder idiom
,因为它既简单又能提供很好的性能。