Java内存模型为何允许读取操作观测未来写入操作?
问题背景
Java内存模型在§17.4.8中明确指出:
尽管允许读取观测执行顺序中后续的写入有时并不理想,但这在某些情况下是必要的。
它引用了§17.4.5中的示例来佐证这个结论,示例程序如下:
| Thread 1 | Thread 2 |
|---|---|
| B = 1; | A = 2; |
| r2 = A; | r1 = B; |
对应的一种特殊执行轨迹:
1: r2 = A; // 观测到A=2的写入 3: r1 = B; // 观测到B=1的写入 2: B = 1; 4: A = 2;
规范解释称:
该轨迹要求某些读取操作观测执行顺序中后续的写入。由于每个线程中读取操作先执行,执行顺序的首个操作必须是读取。若该读取无法观测后续写入,则只能读取变量的初始值,这显然无法覆盖所有行为。
用户的疑问是:为什么不能直接将这种“读取观测后续写入”的执行判定为非法?比如通过以下执行顺序也能得到r1 == 1和r2 == 2的结果:
B = 1; A = 2; r2 = A; // 观测到A=2的写入 r1 = B; // 观测到B=1的写入
如果限制内存模型禁止这种行为,会有什么功能丢失或问题?
核心解释
1. 平衡性能与语义的核心需求
Java内存模型的设计目标之一,是在保证程序语义正确性的前提下,尽可能给编译器、处理器留出优化空间——而指令重排是现代硬件和编译器提升性能的核心手段。
示例中,线程1的B=1和r2=A之间没有数据依赖,线程2的A=2和r1=B之间也没有数据依赖。编译器/处理器完全有权重排这些指令的执行顺序,来提升执行效率。如果禁止“读取观测后续写入”,那么当指令被重排后,读操作只能读到变量的初始值(0),这就会把r1=1、r2=2这种合理的结果排除在外,违背了程序员的预期。
2. 合法执行顺序并非唯一
你提到的那种“先写后读”的全局执行顺序,确实能得到目标结果,但它只是众多合法执行顺序中的一种。Java内存模型需要允许所有不违反线程内语义且结果符合预期的执行,而不是强制要求只能按某一种固定顺序执行。
如果禁止“读取观测后续写入”,就相当于强制线程间的操作必须严格遵循某种全局顺序,这会彻底剥夺编译器和处理器的指令重排能力,导致程序性能大幅下降——这在现代高性能计算场景中是不可接受的。
3. 与happens-before关系的兼容
Java内存模型的核心是happens-before关系,但它并不要求操作的实际执行顺序必须和happens-before顺序完全一致,只要最终结果符合happens-before的语义即可。
在那个特殊的执行轨迹中,虽然实际执行顺序是“读在前、写在后”,但读操作看到了后续的写,最终结果和按happens-before顺序执行的结果一致,所以是合法的。如果禁止这种情况,就会把happens-before关系和实际执行顺序绑定死,违背了内存模型的设计初衷。
内容的提问来源于stack exchange,提问作者yaskovdev

