You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java双重检查锁定模式中tempWrapper为何是正确性必需的?

为什么双重检查锁定中使用FinalWrapper必须用局部变量tempWrapper?

先看两种实现的对比,先放正确的带局部变量的代码:

public class DoubleCheckedLocking {
    private static FinalWrapper<Helper> helperWrapper;

    public static Helper getHelper() {
        // 必须的局部变量
        FinalWrapper<Helper> tempWrapper = helperWrapper;
        if (tempWrapper == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (helperWrapper == null) {
                    helperWrapper = new FinalWrapper<>(new Helper());
                }
                // 同步块内更新局部变量
                tempWrapper = helperWrapper;
            }
        }
        return tempWrapper.value;
    }

    private static class FinalWrapper<T> {
        public final T value;
        public FinalWrapper(T value) {
            this.value = value;
        }
    }
}

如果直接去掉局部变量,改成下面的代码,会存在潜在问题:

// 错误实现,可能失效
public static Helper getHelper() {
    if (helperWrapper == null) {
        synchronized (DoubleCheckedLocking.class) {
            if (helperWrapper == null) {
                helperWrapper = new FinalWrapper<>(new Helper());
            }
        }
    }
    return helperWrapper.value;
}

核心原因:Java内存模型允许的读重排序

Java内存模型(JMM)不会强制约束线程对共享变量的多次读取顺序,甚至允许把helperWrapper.value的读取操作拆解后重排序。具体来说:

读取helperWrapper.value会被拆成两步:

  1. 读取共享变量helperWrapper的引用到线程寄存器
  2. 读取该引用指向对象的value字段

JMM允许把**第一步(读helperWrapper引用)**提前到if (helperWrapper == null)的检查之前执行。这就会引发问题:

  • 线程B先执行第一步,读到helperWrapper的旧值(null),暂存在寄存器里
  • 此时线程A完成了helperWrapper的初始化,把非null的引用写入主内存
  • 线程B再执行if (helperWrapper == null)检查,读到主内存里的非null值,跳过同步块
  • 最后线程B用寄存器里缓存的旧null引用去读value字段,直接抛出NullPointerException

局部变量tempWrapper的作用

用局部变量后,所有操作都基于线程私有的局部变量,彻底避免了共享变量读取的重排序问题:

  1. 第一次读取共享变量helperWrapper到局部变量tempWrapper,这是线程私有的,JMM不会对局部变量的操作做重排序
  2. 后续的null检查和value读取都基于同一个局部变量,要么是null(进入同步块完成初始化),要么是同步块内更新后的正确引用
  3. 同步块内重新赋值tempWrapper = helperWrapper,保证拿到的是最新初始化后的FinalWrapper引用,其value字段因final语义已经安全发布,不会出现未初始化的情况

你之前的理解偏差

你提到"若其他线程读到helperWrapper不为null,那么value的值必然是正确的"——这个结论本身是对的,但问题出在你无法保证读取helperWrapper.value时,用的是和之前null检查时同一个helperWrapper引用。JMM的读重排序可能让你在检查时读到非null,但读value时用的是更早读到的null引用,这才是风险点。

内容的提问来源于stack exchange,提问作者lol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:05:54