线程运行时memory.a修改值为何刷新至主存的技术咨询
问题描述
线程运行时,普通变量会被刷新至主内存?
据我理解,线程未停止时,memory.a应处于工作缓存(worker cache)中,示例代码如下:
public class SynchronizedMemory { // non-visible private int a = 0; public SynchronizedMemory(int a) { this.a = a; } private static void test2() throws InterruptedException { SynchronizedMemory memory = new SynchronizedMemory(1); Object lock = new Object(); new Thread(() -> { System.out.println("thread1 start................"); while (memory.a != -1) { // When I comment out this line, the thread never jumps out of the loop synchronized (lock){} } System.out.println("thread1 stop................"); },"thread1").start(); // wait for thread1 to run Thread.sleep(1000); new Thread(() -> { memory.a = -1; // keep the thread alive, prevent it from being completed and refresh the changes back to main memory while (true){} },"thread2").start(); } }
据我理解,thread2未停止时,
memory.a的修改值应仍处于工作缓存中。但运行上述代码后,thread1退出循环并结束,这表明thread2中memory.a的修改值在thread2仍运行时就已刷新至主内存。请解释该现象。
问题解答
首先要纠正一个核心误解:Java内存模型(JMM)并没有规定线程只有结束时才会把工作缓存的变量刷回主内存。线程运行过程中,多种场景都会触发变量同步到主内存的操作,同时thread1的空同步块是让它能读到最新值的关键,具体拆解如下:
thread2的写操作没有被限制在工作缓存
memory.a = -1是普通写操作,JMM只保证最终会刷回主内存,但不限制时机。实际运行时,CPU缓存策略、JVM的内存调度、甚至系统内存压力,都可能导致这个修改立刻同步到主内存——线程持续运行不代表变量会一直锁在工作缓存里。thread1的空同步块强制了主内存读取
while循环里的synchronized (lock){}空同步块,触发了JMM的happens-before规则:- 进入同步块前,thread1会失效自身工作缓存的对应数据,必须从主内存重新读取
memory.a的值; - 退出同步块时,会把线程本地的修改刷回主内存(这里虽然没有修改,但规则依然生效)。
正是这个同步操作,让thread1每次循环都会去主内存拉取最新的memory.a值,所以能及时看到thread2的修改并退出循环。
- 进入同步块前,thread1会失效自身工作缓存的对应数据,必须从主内存重新读取
去掉同步块后循环无法退出的原因
如果注释掉空同步块,JVM会做缓存优化和指令重排:由于循环内没有任何同步操作,JVM会判定memory.a的值不会被其他线程修改,直接把值缓存到CPU寄存器里,不再去主内存读取,导致thread1永远看不到thread2的修改,陷入死循环。
内容的提问来源于stack exchange,提问作者Ysngzhou Liao

