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

Java内存模型:线程间赋值可见性的保障分析及延伸疑问

Java内存模型下的线程可见性:除start()外的线程启动场景分析

先看你给出的这段代码:

public class Test {
    private int i = 1;
    public void f() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (i != 2) throw new AssertionError("i != 2");
            }
        };
        i = 2;
        new Thread(runnable).start();
    }
}

你提到的结论完全正确:这段代码里run()方法中的i必然是2。因为Java内存模型(JMM)明确规定:线程的start()调用先行发生于(happens-before)该线程内的所有操作。主线程里的i=2赋值操作,会被新启动的线程完全可见,不会触发断言错误。

那核心问题来了:如果不用start()这种标准方式触发线程逻辑,赋值操作的可见性还能得到保障吗?答案是几乎都不能,下面分几种常见情况拆解:

直接调用Runnable的run()方法

如果把new Thread(runnable).start()改成runnable.run(),这根本没有启动新线程——run()方法是在主线程里同步执行的,这种情况下自然不存在可见性问题,但这本质上不是多线程场景,和原问题的前提不符。

绕过start()直接调用Thread的run()方法

要是你写了Thread t = new Thread(runnable); t.run();,结果和上面一样:run()是在当前线程执行,没有真正启动新线程,同样不涉及多线程可见性问题,但这是错误的线程使用方式,完全违背了Thread类的设计意图。

使用非标准的线程任务触发机制(比如自定义调度)

如果是通过某种自定义方式让线程执行Runnable逻辑(比如提前初始化好线程池里的线程,之后手动触发它执行任务),这种情况下没有JMM的happens-before规则兜底。主线程的i=2赋值,对执行run()的线程来说完全可能不可见:

  • 主线程的赋值可能只存在于自己的工作内存,还没刷回主内存;
  • 执行线程可能已经缓存了i的旧值(1),不会去主内存重新读取。

这时候你大概率会看到AssertionError抛出,因为线程看到的还是i的旧值。

手动保障可见性的替代方案

如果不能依赖start()的happens-before规则,你需要手动添加可见性保障手段:

  • 把i声明为volatile:强制所有线程直接从主内存读写i的值;
  • 用synchronized块包裹赋值和读取操作:同步块的进入/退出会触发内存屏障,保证可见性;
  • 使用java.util.concurrent包下的原子类(比如AtomicInteger):这类类的操作本身就具备可见性和原子性。

总结一下:只有通过Thread.start()启动新线程的场景,JMM才会自动保障启动前的操作对新线程可见;任何其他非标准的线程任务触发方式,都需要手动添加可见性保障,否则极大概率出现线程安全问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:22:26