单线程写多线程读场景下,volatile是否始终必要?
问题分析:无volatile时子线程能否可靠读取变量更新值
你的判断存在潜在风险——虽然实际运行中大概率能读到1,但从Java内存模型(JMM)的规范角度,这段代码不保证子线程一定能读取到更新后的变量值,编译器和处理器的优化完全可能干扰最终结果。
核心原因:缺少Happens-Before关系
在没有volatile、锁或其他同步机制的情况下,主线程对completed的写入操作,和子线程对completed的读取操作之间没有JMM定义的happens-before关系。这意味着JMM不要求主线程的写操作结果必须对其他线程可见,哪怕代码的执行顺序看起来是“先写后读”。
可能的优化干扰场景
- 编译器优化:编译器可能将
completed的读写操作局限于寄存器,不及时刷回主存。比如主线程把completed=1写入寄存器后,没有同步到主存,子线程读取的就是主存里的初始值0;极端情况下,编译器甚至可能认为该写入对主线程无影响,直接将写操作优化掉。 - 处理器指令重排与缓存一致性:处理器可能对内存访问指令进行重排,或者因为缓存行局部性优化,主线程的缓存更新没有及时同步到主存。哪怕子线程延迟5ms读取,只要没有触发缓存同步的机制(比如内存屏障),子线程的缓存依然可能保留旧值。
为什么实际运行可能“看起来正常”?
你测试时大概率能读到1,是因为Executors的线程池实现内部(比如schedule、shutdown、awaitTermination方法)可能包含了同步操作(比如锁、volatile变量读写),这些操作会自动触发内存屏障,间接把主线程对completed的写入同步到主存。但这是依赖线程池实现细节的侥幸行为,不是JMM的规范保证——一旦线程池实现变更、JVM版本更换,这种间接的内存屏障可能消失,代码就会出现读取旧值的问题。
结论
从Java并发规范的严谨性出发,必须给completed加上volatile关键字,才能可靠保证子线程读取到主线程更新后的变量值。不能依赖“延迟5ms”或第三方组件的内部同步来替代显式的可见性保证。
内容的提问来源于stack exchange,提问作者Jacky Wang
相关产品推荐
相关产品推荐

