编程语言
首页 > 编程语言> > Java-监视器解锁之前发生的关系

Java-监视器解锁之前发生的关系

作者:互联网

我最近阅读了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()

如果新的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是分配,最终输出都不会改变.因此重新排序是完全可能的.

> monitor.unlock

根据将print语句“拉入”同步块的示例,让我们尝试扭转这种情况,即使用

synchronized{
    a = 5;
    print a;
}

我可以期望编译器执行此操作:

synchronized{
    a = 5;
}
 print a;

在单线程世界中看起来是完全合理的,但是对于JLS(根据引用的来源),这绝对是无效的.现在,如果在JLS中找不到关于此的任何内容,为什么会这样呢?显然,关于“程序顺序”的动机现在已经不相关了,因为编译器可以进行重新编译,例如将语句“拉入”到同步块中.

解决方法:

这不仅是在同步块内执行的所有动作,还指的是MonitorExit之前该线程的所有动作.

Could someone explain how JLS explains the obvious condition that all
the actions within the synchronization block must happen-before the
unlock operation?

对于一个特定的线程(只有一个线程),所有操作(无论是否同步)都保持程序顺序,因此看起来所有读取和写入都是按顺序进行的(在单线程情况下,在顺序之前我们不需要发生操作).

事前发生关系考虑了多个线程,也就是说,在连续的monitorenter之后,monitorexit之前在一个线程中发生的所有操作对于所有线程都是可见的.

编辑以解决您的更新.

编译器必须遵循某些特定规则才能重新排序.在这种情况下,特定的一种在here的“可重新订购”网格中得到了证明.

特别有用的条目是

>第一步:正常加载(加载a;打印a)
>第二步:监控退出

这里的值为No表示编译器无法对两个操作进行重新排序,其中第一个是正常加载,第二个是monitorexit,因此在您的情况下,此重新排序将违反JLS.

有一种规则称为蟑螂汽车旅馆排序,即可以将读/写重新排序为同步块,但不能重新排序.

标签:multithreading,happens-before,java,memory-model,jls
来源: https://codeday.me/bug/20191122/2057026.html