Java中Thread.yield是否保证刷新内存读写、符合JMM内存屏障规范?
核心结论
- 只有
volatile、正确使用的synchronized这些JMM明确规定的同步机制,才有可见性的官方保证。Thread.yield()、println()这类操作带来的可见性效果属于JVM实现的附带行为,没有任何JMM层面的规范保证,不能作为正式的同步方案依赖。
1. 对volatile和synchronized的认知修正
你的部分理解是符合JMM规范的:
- 对
volatile变量的写入,会和后续任意线程对该变量的读建立happens-before关系,写入的值对读线程立即可见,所以给keepGoing加volatile是100%可靠的解决方案。 - 正确加锁/解锁的
synchronized块也有happens-before保证:同一个锁的解锁操作,happens-before于后续对该锁的加锁操作,解锁前的所有写入对加锁后的代码立即可见。你注释里的空同步块确实能解决该场景的问题,但注意只有两个线程竞争同一个锁的时候才生效,并不是空同步块本身无条件刷新所有缓存。
2. println生效的原因
println的底层实现自带同步逻辑,参考JDK中PrintStream的源码,println方法本身是加了synchronized修饰的:
public void println() { synchronized (this) { newLine(); } }
相当于你在循环里每次都执行了一个加锁操作,刚好触发了JVM的缓存刷新,让你读到了主线程修改的keepGoing值。但这个是println内部同步机制带来的效果,不是IO操作本身有内存屏障的规范保证。
3. yield生效的原因
Thread.yield()在JMM里没有任何和可见性相关的规范约束。你测试时看到的效果,完全是特定JVM实现的附带行为:比如有些JVM实现里,线程让步的时候会顺带刷新本地缓存,或者JIT编译器在遇到yield的时候不会做激进的提升优化(比如把keepGoing提到循环外变成寄存器变量,永远不重读主内存)。这种效果在不同厂商、不同版本的JVM,甚至不同的运行参数下都可能失效,完全不可靠。
4. 实践建议
不要依赖任何非JMM规范保证的“巧合”行为来处理并发可见性问题。对于这种循环标记位的场景,官方推荐的可靠方案只有两种:
- 给标记变量加
volatile修饰 - 每次读写标记变量的时候都用同一个锁做同步
内容的提问来源于stack exchange,提问作者Gonen I
相关产品推荐
相关产品推荐

