首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >单例模式中的Double-Checked:volatile与两次判断的作用是?

单例模式中的Double-Checked:volatile与两次判断的作用是?

作者头像
孟君
发布2025-11-17 19:59:35
发布2025-11-17 19:59:35
230
举报
在Java开发中,单例模式是我们最常用的设计模式之一。但你是否曾思考过:为什么双重检查锁定(double-check)需要两次判空?volatile关键字在这里又扮演着什么关键角色?

01、什么是双重检查?

双重检查是一种用于单例模式的延迟初始化技术,它既保证了线程安全,又避免了每次获取实例时的同步开销.

代码实现

02、两次判断的作用解析

第一次判断:性能优化

代码语言:javascript
复制
if (instance == null) {  // 第一次检查
    synchronized (Singleton.class) {
        // ...
    }
}

作用:避免不必要的同步等待

  • 当实例已经创建后,大部分线程会直接返回已存在的实例
  • 只有第一次创建时需要进入同步块
  • 大大提高了并发访问时的性能

第二次判断:线程安全保证

代码语言:javascript
复制
synchronized (Singleton.class) {
    if (instance == null) {  // 第二次检查
        instance = new Singleton();
    }
}

作用:防止重复创建实例

  • 多个线程可能同时通过第一次检查
  • 在等待锁的过程中,其他线程可能已经创建了实例
  • 第二次检查确保即使多个线程进入同步块,也只有一个线程能创建实例

03、volatile的关键作用

为什么要用volatile?

代码语言:javascript
复制
private static volatile Singleton instance;

volatile在这里解决了指令重排序问题,这是很多开发者容易忽略的关键点。

问题根源:对象创建的非原子性

我们以为的instance = new Singleton()是原子操作,但实际上它包含三个步骤:

  1. 分配内存空间
  2. 初始化对象(调用构造函数)
  3. 将引用指向内存地址

危险场景:

代码语言:javascript
复制
// 如果没有volatile,可能发生指令重排序:
// 步骤1:分配内存
// 步骤3:引用指向内存(此时instance != null,但对象未初始化)
// 步骤2:初始化对象

// 线程A执行到步骤3时,线程B看到instance不为null
// 直接返回未完全初始化的对象,导致程序错误

volatile的解决方案

  • 禁止指令重排序:确保对象的初始化在引用赋值之前完成
  • 保证可见性:确保一个线程修改instance后,其他线程立即看到最新值

04、小结

双重检查是一个经典的优化方案,它:

  1. 通过两次判断:平衡了性能与线程安全
  2. 通过volatile:解决了指令重排序带来的隐患
  3. 适用于:需要延迟初始化且对性能要求较高的场景
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码实现
    • 第一次判断:性能优化
    • 第二次判断:线程安全保证
    • 为什么要用volatile?
    • 问题根源:对象创建的非原子性
    • volatile的解决方案
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档