Java:获取实例变量锁时JVM刷新整个实例还是仅该变量状态?
首先直接给结论:是的,当线程获取任意一个锁(哪怕是实例里的某个成员变量锁)时,会触发该线程本地缓存的整体刷新,所有共享变量都会从主存重新读取,而不仅仅是锁对象本身的状态。下面结合Java内存模型和你的代码来拆解:
1. Java内存模型的核心规则
Java里每个线程都有自己的本地缓存,共享变量存在主存中。默认情况下,线程对共享变量的读写都是在本地缓存中进行,不会立刻同步到主存——这就是初始测试里T2读到过期flag值的原因:T1修改的flag还在自己的本地缓存,没同步到主存,T2一直读自己的缓存副本。
而同步操作(包括synchronized加锁/解锁、volatile变量的读写)会触发内存屏障,强制线程的本地缓存和主存同步:
- 当线程获取锁时:它的本地缓存会失效,后续所有对共享变量的读取都必须从主存重新获取。
- 当线程释放锁时:它本地缓存中所有修改过的共享变量都会被刷新到主存。
2. 锁的happens-before关系
按照Java语言规范的定义,锁的解锁操作和后续同一个锁的加锁操作之间存在happens-before关系。这意味着:
- 所有在解锁前对共享变量的修改,对后续获取该锁的线程都是可见的。
- 这里的“共享变量”是指所有被该线程访问过的共享变量,不是只和锁对象相关的变量。
回到你的代码:
T1调用setFlag()修改了flag(虽然没加锁,但后续T2获取锁时,会强制从主存读取所有共享变量,自然包括flag的最新值)。当T2执行refreshLock()获取lock锁时,它的本地缓存被清空,接下来调用getFlag()就会从主存读取T1修改后的flag值。
3. 为什么读取volatile变量也有同样效果?
你新增的visibility是volatile变量,读取volatile变量同样会触发内存屏障:线程在读取volatile变量后,后续的所有共享变量读取都必须从主存获取。所以当T2执行refreshVisibility()读取visibility时,相当于触发了缓存刷新,之后读flag也能拿到最新值。
4. 测试代码验证
你的测试里,当注释掉sObject.refreshLock()时,T2的while循环一直读自己本地缓存里的flag=false,所以无限循环;当放开注释,T2每次循环都会获取锁,触发缓存刷新,就能读到T1在1秒后修改的flag=true,线程自然结束。
内容的提问来源于stack exchange,提问作者jpact

