多volatile字段为何能纳入同步顺序?基于JCStress测试的疑问
为什么VolatileDekker测试中"0,0"结果被禁止?
你的疑惑点在于:既然volatile写允许后续指令重排到写之前,那线程的x=1; r1=y;会不会被重排成r1=y; x=1;,两个线程都这么做就会出现r1=0、r2=0的结果?但实际这个结果被Java内存模型(JMM)禁止,核心原因是volatile操作的同步顺序(synchronization order)有两个硬约束:
- 所有volatile的读写操作构成一个全序关系:所有线程的volatile操作都能被排成一个单一的线性顺序,每个操作在这个顺序里有明确的先后位置。
- 这个全序必须符合线程内的程序顺序:对于单个线程来说,它的volatile操作在同步顺序中的先后顺序,必须和代码编写顺序一致。比如线程1先写
x再读y,那在同步顺序里,写x必须排在读y的前面,绝对不能反过来。
现在我们假设存在"0,0"的结果,也就是r1=0且r2=0,这意味着:
- 线程1读
y时看到初始值0——说明在同步顺序中,线程1的读y操作排在线程2写y操作的前面。 - 线程2读
x时看到初始值0——说明在同步顺序中,线程2的读x操作排在线程1写x操作的前面。
但结合程序顺序约束:
- 线程1的写
x必须排在它的读y前面; - 线程2的写
y必须排在它的读x前面。
把这些顺序连起来会形成矛盾的循环:线程1写x → 线程1读y → 线程2写y → 线程2读x → 线程1写x——这显然违反了全序的线性要求(不可能存在循环)。所以这种情况在JMM中是不可能发生的。
再回到你提到的《Java Concurrency in Practice》里的happens-before规则:它描述的是volatile读写带来的跨线程可见性,但并没有覆盖同步顺序的全序约束。同步顺序的程序一致性要求,直接禁止了"先读后写"这种违反线程内程序顺序的重排——因为这种重排会导致线程的volatile操作在同步顺序中颠倒,破坏全序的合理性。
简单来说:JMM不仅要求volatile读写的可见性,还通过同步顺序的全序+程序顺序一致性,确保了两个线程的volatile写操作,至少有一个会被另一个线程的读操作看到,从而杜绝了"0,0"的结果。
内容的提问来源于stack exchange,提问作者Piotr Michalczyk
相关产品推荐
相关产品推荐

