双重检查锁定失败可能性——参照《The "Double-Checked Locking is Broken" Declaration》

双重检查锁定在延迟初始化的单例模式中见得比较多(单例模式实现方式很多,这里为说明双重检查锁定问题,只选取这一种方式),先来看一个版本:

 

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){}

   

    public static Singleton  getInstance() {

       if(instance == null) {

           instance = new Singleton();

       }

       return instance;

    }

}

上面是最原始的模式,一眼就可以看出,在多线程环境下,可能会产生多个Singleton实例,于是有了其同步的版本:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){}

   

    public synchronized static Singleton getInstance() {

       if(instance == null) {

           instance = new Singleton();

       }

       return instance;

    }

}

在这个版本中,每次调用getInstance都需要取得Singleton.class上的锁,然而该锁只是在开始构建Singleton 对象的时候才是必要的,后续的多线程访问,效率会降低,于是有了接下来的版本:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){}

   

    public static Singleton getInstance() {

       if(instance == null) {

           synchronized(Singleton.class) {

              if(instance == null) {

                  instance = new Singleton();

              }

           }

       }

       return instance;

    }

}

很好的想法!不幸的是,该方案也未能解决问题之根本:

 

原因在于:初始化Singleton  和 将对象地址写到instance字段 的顺序是不确定的。在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。

 

鉴于以上原因,有人可能提出下列解决方案:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){}

   

    public static Singleton getInstance() {

       if(instance == null) {

           Singleton temp;

           synchronized(Singleton.class) {

              temp = instance;

              if(temp == null) {

                  synchronized(Singleton.class) {

                     temp = new Singleton();

                  }

                  instance = temp;

              }

           }

       }

       return instance;

    }

}

该方案将Singleton对象的构造置于最里面的同步块,这种思想是在退出该同步块时设置一个内存屏障,以阻止初始化Singleton  和 将对象地址写到instance字段 的重新排序。

 

不幸的是,这种想法也是错误的,同步的规则不是这样的。退出监视器(退出同步)的规则是:所以在退出监视器前面的动作都必须在释放监视器之前完成。然而,并没有规定说退出监视器之后的动作不能放到退出监视器之前完成。也就是说同步块里的代码必须在退出同步时完成,而同步块后面的代码则可以被编译器或运行时环境移到同步块中执行

 

编译器可以合法的,也是合理的,将instance = temp移动到最里层的同步块内,这样就出现了上个版本同样的问题。

 

在JDK1.5及其后续版本中,扩充了volatile语义,系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序。

 

在jdk1.5及其后的版本中,可以将instance 设置成volatile以让双重检查锁定生效,如下:

public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton(){}

   

    public static Singleton getInstance() {

       if(instance == null) {

           synchronized(Singleton.class) {

              if(instance == null) {

                  instance = new Singleton();

              }

           }

       }

       return instance;

    }

}

 

需要注意的是:在JDK1.4以及之前的版本中,该方式仍然有问题。


原文地址:http://freish.iteye.com/blog/1008304

<think>我们之前已经介绍了双重检查锁定在单例模式中的应用。现在我们来探讨双重检查锁定适用的场景以及具体的例子。 双重检查锁定(Double-Checked Locking)主要用于减少同步的开销,尤其是在初始化某个资源(如创建单例对象)时。它适用于以下场景: 1. 需要延迟初始化(Lazy Initialization)且初始化成本较高的资源。 2. 需要线程安全地初始化共享资源,但后续访问不需要同步。 3. 希望降低同步带来的性能开销(避免每次访问都进行同步)。 下面我们举一个具体的例子:缓存系统的初始化。 假设我们有一个缓存管理器(CacheManager),它负责加载配置并初始化缓存。初始化过程比较耗时,所以我们希望在第一次使用时才初始化(懒加载),并且要保证多线程环境下只初始化一次。 使用双重检查锁定实现: </think>### 双重检查锁定(Double-Checked Locking)的应用场景与实例 双重检查锁定主要用于解决**高并发环境下资源初始化的线程安全问题**,特别适合以下场景: #### ✅ 典型应用场景 1. **延迟初始化(Lazy Initialization)** - 当初始化成本昂贵(如数据库连接、大型对象) - 且可能不会立即使用时,避免不必要的资源消耗 2. **单例模式实现** - 需要全局唯一实例的场景 - 实例初始化开销大需延迟创建 3. **资源池管理** - 线程池/连接池的初始化 - 缓存系统的启动 4. **配置加载** - 全局配置文件的加载解析 - 避免多线程重复加载 #### 📝 实际代码示例:数据库连接管理器 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DatabaseConnectionManager { // volatile保证可见性和禁止指令重排序 private static volatile Connection instance; // 私有构造器防止外部实例化 private DatabaseConnectionManager() {} public static Connection getConnection() { // 第一次检查:避免不必要的同步 if (instance == null) { synchronized (DatabaseConnectionManager.class) { // 第二次检查:防止重复创建 if (instance == null) { try { // 创建高成本数据库连接 String url = "jdbc:mysql://localhost:3306/mydb"; String user = "root"; String password = "password"; instance = DriverManager.getConnection(url, user, password); System.out.println("Database connection established"); } catch (SQLException e) { throw new RuntimeException("Failed to create DB connection", e); } } } } return instance; } } ``` #### 🌟 场景优势分析 1. **性能优化** ```java // 99%的调用直接返回实例,避免同步锁开销 if (instance != null) return instance; ``` 2. **线程安全** ```java synchronized (...) { // 保证只有一个线程执行初始化 if (instance == null) { // 二次检查防重复创建 instance = new ... // 安全创建 } } ``` 3. **资源节约** ```java // 避免每次调用都创建连接 // 只有当真正需要时才初始化 ``` 4. **延迟加载** ```java // 系统启动时不立即初始化 // 首次调用getConnection()时才创建 ``` #### ⚠ 使用注意事项 1. **必须使用`volatile`**(JDK 5+) 防止指令重排序导致返回未完全初始化的对象 2. **初始化逻辑不可抛异常** 静态实例初始化失败会导致后续调用永远失败 3. **不适合简单对象** 轻量级对象可直接用静态内部类方式: ```java public class Singleton { private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值