Java-发生在监视器解锁的关系之前
我最近读了一篇文章,清楚地描述了Java内存模型的许多本质。有一段特别的摘录引起了我的注意,那就是:Java-发生在监视器解锁的关系之前,java,multithreading,memory-model,jls,happens-before,Java,Multithreading,Memory Model,Jls,Happens Before,我最近读了一篇文章,清楚地描述了Java内存模型的许多本质。有一段特别的摘录引起了我的注意,那就是: The rule for a monitorexit (i.e., releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. 对我来说似乎很明显,但是阅读了定义之前发生的事情,我所能找到的关于监视器解除阻止的所有信息是当
The rule for a monitorexit (i.e., releasing synchronization) is that
actions before the monitorexit must be performed before the monitor is released.
对我来说似乎很明显,但是阅读了定义之前发生的事情,我所能找到的关于监视器解除阻止的所有信息是当一个线程解锁监视器时,另一个线程再次锁定监视器之前发生的事情(这也很有意义)。有人能解释一下JLS是如何解释同步块中的所有操作必须在解锁操作之前发生的明显情况的吗
进一步评论:
根据几条回复,我想对你们所说的话写下进一步的评论:
a = new A()
如果newa()
涉及一百个操作,然后将堆上的地址分配给A
,编译器只需对这些操作重新排序,将堆地址分配给A
,然后执行通常的初始化(双重检查锁定的问题)
可以改为
synchronized{
a = 5;
print a;
}
因此,我们使用print语句对monitorexit重新排序(根据JLS也有效)
现在,我提到的简单案例是:
x = 1;
y = 2;
c = x + y;
print c;
我看不出有什么理由阻止编译器先指定x或先指定y。没有什么可以阻止它,因为不管是先指定x还是先指定y,最终输出都是不变的。因此,重新排序是完全可能的
synchronized{
a = 5;
print a;
}
我希望编译器能做到这一点:
synchronized{
a = 5;
}
print a;
在单线程世界中似乎完全合理,然而这肯定是无效的,而且是针对JLS的(根据引用的来源)。如果我在JLS中找不到与此相关的任何信息,那么为什么会出现这种情况?显然,关于“程序顺序”的动机现在无关紧要了,因为编译器可以进行重新派生,例如将语句“拉入”到同步块中。不仅仅是在
同步块中执行的所有操作,它还引用该线程在执行monitorexit
之前执行的所有操作
有人能解释一下JLS是如何解释所有
同步块中的操作必须在
解锁操作
对于一个特定的线程(并且只有一个线程),不管synchronized
如何,所有的操作都会维护程序顺序,因此看起来好像所有的读写都是按顺序发生的(在单线程的情况下,我们不需要先发生再排序)
“先发生后发生”关系考虑多个线程,即在连续的monitorenter
之后,monitorexit之前在一个线程中发生的所有操作对所有线程都可见
编辑以解决更新问题
编译器必须遵循某些特定规则才能重新排序。本例中的具体示例在找到的Can重新排序网格中进行了演示
特别有用的条目包括
- 第一个动作:正常加载(加载a;打印a)
- 第二个操作:监视器退出
此处的值为No,这意味着编译器无法对两个操作重新排序,其中第一个是正常加载,第二个是monitorexit
,因此在您的情况下,此重新排序将违反JLS
有一条规则称为roach motel排序,即读/写可以重新排序到同步块中,但不能从同步块中取出 可能您错过了(§17.4.5):
如果x和y是同一线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)
结合您之前已经知道的情况,应该很清楚,这意味着解锁操作之前的所有操作对另一个线程都是可见的
关于你对问题的补充,如果你写下:
synchronized {
a = 5;
b = 3;
}
编译器会发出以下信息:
synchronized{
a = 5;
}
b = 3;
然后,我上面引用的规定被违反了:现在b=3
在锁释放之前不会发生。这就是为什么它是非法的。(请注意,您关于打印a的示例并不具有指导意义,因为它只涉及阅读+副作用,而不容易用简单变量描述。)我不同意您所说的大部分内容。如果我有一个程序a=b;b=1;c=a+b;print c
编译器可以自由地对前两条语句重新排序。因此,监视器退出之前的操作可能会被重新排序,同时,monitorexit之后的一些操作可能会被重新排序,以在monitorexit之前发生(引用源代码)。但很显然,在monitorexit之前的行动不可能在之后发生,这正是我不理解的基础JLS@Bober02不,编译器无法对这些语句重新排序,因为它会将行为从print initial\u value\u of_b+1
更改为print 2
@Bober02 Pete完全正确。为了让JIT对任何语句重新排序,它必须首先不影响程序本身(维护程序顺序)。如果运行的单个线程的重新排序输出不同于违反JIT的情况。@Bober02如果您能更详细地说明您的担忧,您能否在问题中包含相应的monitorenter和monitorexit(或synchronized
)@PeteKirkham我想您没有领会Bober02的要点。。。动作可能不会因原因而重新排序,但它们可能会因时间而重新排序。如果临时重新排序,那么另一个能够观察到临时排序的线程将看到超出程序顺序的操作。我试图在对初始问题的进一步评论中解释为什么这不能说服我。另外,重新排序是非法的,正是因为它违反了
synchronized{
a = 5;
}
b = 3;