你提供的代码片段展示了一种常见的设计模式,称为“双重检查锁定”(Double-Checked Locking),用于实现线程安全的延迟初始化(Lazy Initialization)。在这个模式中,synchronized块用于确保在多线程环境中只创建一次Helper实例。然而,这种实现方式在 Java 中存在一些潜在的问题,特别是在 Java 1.5 之前的版本中。
问题分析
-
指令重排序问题:
- 在 Java 中,对象的创建和赋值操作可能会被 JIT 编译器重排序。具体来说,以下三个步骤可能会被重排序:
- 分配内存给
Helper对象。 - 调用
Helper的构造函数初始化对象。 - 将
helper引用指向分配的内存。
- 分配内存给
- 如果发生重排序,可能会导致
helper在构造函数完成之前就被赋值,这意味着另一个线程可能会看到一个部分构造的对象,并返回未完全初始化的Helper实例。
- 在 Java 中,对象的创建和赋值操作可能会被 JIT 编译器重排序。具体来说,以下三个步骤可能会被重排序:
-
缺少
volatile关键字:- 在 Java 5 及以上版本中,通过在
helper变量前添加volatile关键字,可以防止指令重排序。volatile关键字确保对helper的读写操作具有可见性和有序性。
- 在 Java 5 及以上版本中,通过在
修正代码
为了避免这些问题,可以使用volatile关键字来确保正确的双重检查锁定:
public class LazyInitDemo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) { // First check (no locking)
synchronized (this) {
if (helper == null) { // Second check (with locking)
helper = new Helper();
}
}
}
return helper;
}
// other methods and fields...
}
class Helper {
// Helper class implementation
}
其他注意事项
- 性能:双重检查锁定模式在 Java 5 及以上版本已经是一个有效的性能优化策略,因为它减少了获取锁的开销。
- 替代方案:如果不希望使用双重检查锁定模式,另一种常见的替代方案是使用静态内部类来实现延迟初始化,这种方式天然地是线程安全的,并且不需要显式的同步。
静态内部类实现示例
public class LazyInitDemo {
private static class HelperHolder {
private static final Helper INSTANCE = new Helper();
}
public static Helper getHelper() {
return HelperHolder.INSTANCE;
}
// other methods and fields...
}
使用静态内部类的方式利用了类加载机制来确保线程安全,同时也实现了延迟初始化。这个方法简单且高效,通常被认为是最佳实践之一。
浙公网安备 33010602011771号