登录
首页 >  文章 >  java教程

Double-Checked Locking 与 volatile 实现高效单例

时间:2026-05-13 18:40:43 257浏览 收藏

本文深入剖析了Java中实现懒汉式线程安全单例的经典方案——双重检查锁定(DCL)结合volatile关键字,揭示其背后不可替代的底层原理:volatile不仅确保instance引用的可见性,更通过禁止指令重排序,彻底杜绝其他线程获取到半初始化对象的风险;而两次判空则精妙协同——外层避免无谓加锁提升性能,内层防止多线程重复初始化保证正确性。文章不仅给出JDK 1.5+下最简稳的标准模板,更直击开发中极易忽视的致命陷阱,如volatile修饰位置错误、同步对象误用、构造器内this泄露等,帮你真正理解“为什么必须这么写”,而非仅机械套用代码。

如何利用 Double-Checked Locking 配合 volatile 屏障实现线程安全且高性能的单例加载

直接用双重检查锁定(DCL)+ volatile 是目前 Java 中实现懒汉式线程安全单例最成熟、兼顾性能与正确性的方案。关键不在“用了没”,而在“为什么必须这么写”——漏掉 volatile 或顺序错乱,就可能让其他线程拿到半初始化的对象。

volatile 是内存屏障,不是可有可无的修饰符

没有 volatile,JVM 可能将 new Singleton() 拆成三步:
① 分配内存空间
② 初始化对象字段
③ 将 instance 引用指向该内存地址
而步骤②和③可能被重排序。结果就是:另一个线程看到 instance != null,但实际字段还是默认值(如 int=0、Object=null),一调用就空指针或逻辑错乱。
volatile 强制禁止这种重排序,并保证写操作对所有线程立即可见

双重检查的两次判空缺一不可

第一次判空在 synchronized 外:避免已初始化后每次调用都抢锁,提升高并发下的吞吐量。
第二次判空在 synchronized 内:防止多个线程同时通过第一次检查后,争抢进入同步块,最终只允许一个线程真正构造实例。
两者配合,实现了“只在首次创建时加锁,后续完全无锁访问”的目标。

标准写法模板(Java)

以下是最小、最稳妥的实现,适用于 JDK 1.5+:

public class Singleton {
  private static volatile Singleton instance;

  private Singleton() {}

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class) {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }

容易踩的坑

  • 把 volatile 写在方法参数或局部变量上——无效,必须修饰静态字段
  • 用 synchronized(this) 替代 synchronized(Singleton.class)——this 还没造出来,没法用
  • 在构造函数里泄露 this(比如启动线程、注册监听器)——即使加了 volatile,也可能让其他线程拿到未构造完的对象
  • 用普通 boolean 标志位替代 volatile instance 判空——无法保证可见性,等同于没锁

本篇关于《Double-Checked Locking 与 volatile 实现高效单例》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于文章的相关知识,请关注golang学习网公众号!

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>