我最近阅读了http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,它清楚地描述了 Java 内存模型的许多内在特性。一个特别的摘录引起了我的注意,即:
The rule for a monitorexit (i.e., releasing synchronization) is that
actions before the monitorexit must be performed before the monitor is released.
对我来说似乎很明显,但是在阅读了http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html和发生之前的定义后,我能找到的关于显示器解锁的所有信息都是当一个线程解锁发生的监视器- 在另一个线程再次锁定它之前(这也很有意义)。有人能解释一下 JLS 如何解释同步块中的所有操作都必须在解锁操作之前发生的明显条件吗?
进一步的评论:
基于几个回复,我想对你们一直在说的话写下进一步的评论:
- 在单线程中重新编码
我引用的来源中的几个“真相”:
a = new A()
如果new A()
涉及一百个操作,然后将堆上的地址分配给a
,编译器可以简单地重新排序这些操作以分配堆地址a
,然后按照通常的初始化(双重检查锁定的问题)
synchronized{
a = 5;
}
print a;
可以改为
synchronized{
a = 5;
print a;
}
所以我们monitorexit
用打印语句重新排序(根据 JLS 也有效)
现在,我提到的简单案例:
x = 1;
y = 2;
c = x + y;
print c;
我认为没有理由阻止编译器先分配 x 或先分配 y。完全没有什么可以阻止它,因为无论是先分配 x 还是先分配 y,最终输出都不会改变。所以重新排序是完全可能的。
- 监视器.解锁
基于 print 语句被“拉入”同步块的示例,让我们尝试扭转这一点,即 startwing
synchronized{
a = 5;
print a;
}
我可以期望编译器这样做:
synchronized{
a = 5;
}
print a;
在单线程世界中似乎完全合理,但是这绝对是无效的,并且针对 JLS(根据引用的来源)。如果我在 JLS 中找不到任何关于此的内容,为什么会出现这种情况?显然,关于“程序顺序”的动机现在已经无关紧要了,因为编译器可以进行重新排序,例如将语句“拉入”到同步块中。