在以下代码中:
class A {
private int number;
public void a() {
number = 5;
}
public void b() {
while(number == 0) {
// ...
}
}
}
如果调用方法b,然后启动一个触发方法a的新线程,则方法b不能保证看到number
的更改,因此b
可能永远不会终止。
当然,我们可以number
volatile
来解决此问题。但是出于学术原因,我们假设volatile
不是一个选项:
JSR-133 FAQs告诉我们:
在我们退出synchronized块后,我们释放了监视器,具有将缓存刷新到主内存的效果,因此该线程所做的写操作对其他线程可见。在我们进入同步块之前,我们获取了监视器,具有使本地处理器缓存无效的效果,以便从主存储器重新加载变量。
这听起来我只需要a
和b
进入和退出任何synchronized
- 完全阻止,无论他们使用什么显示器。更确切地说,这听起来像......:
class A {
private int number;
public void a() {
number = 5;
synchronized(new Object()) {}
}
public void b() {
while(number == 0) {
// ...
synchronized(new Object()) {}
}
}
}
...将消除此问题并保证b
会看到对a
的更改,因此最终也会终止。
然而,常见问题解答也明确指出:
另一个含义是,以下模式,有些人 用来强制记忆障碍,不起作用:synchronized (new Object()) {}
这实际上是一个无操作,你的编译器可以完全删除它, 因为编译器知道没有其他线程会同步 同一台显示器。你必须为之建立一个先发生过的关系 一个线程看到另一个的结果。
现在这令人困惑。我认为synchronized-Statement将导致缓存刷新。它肯定不能将缓存刷新到主存储器,因为主存储器中的变化只能由在同一监视器上同步的线程看到,特别是因为对于基本上做同样事情我们不会在甚至需要一台显示器,还是我错了?那么为什么这是一个no-op并且不会导致b
通过保证终止?
答案 0 :(得分:49)
常见问题解答不是此事的权威; JLS是。第17.4.4节指定了同步关系,这些关系提供给先前发生的关系(17.4.5)。相关要点是:
- 监视器上的解锁操作 m 与 m 上的所有后续锁定操作同步(其中“后续”根据同步定义)顺序)。
由于 m 这里是对new Object()
的引用,并且它永远不会存储或发布到任何其他线程,我们可以确定没有其他线程会获得对 m 。此外,由于 m 是一个新对象,我们可以确定之前没有锁定的操作。因此,我们可以确定没有任何操作正式与此操作同步。
从技术上讲,您甚至不需要执行完全缓存刷新以达到JLS规范;它不仅仅是JLS所要求的。一个典型的实现就是这样做的,因为它是硬件允许你做的最简单的事情,但它可以说是“超越”。如果escape analysis告诉优化编译器我们需要更少,编译器可以执行更少的操作。在您的示例中,转义分析可以告诉编译器该操作无效(由于上面的推理)并且可以完全优化。
答案 1 :(得分:20)
以下模式,一些人用来强制记忆障碍,不起作用:
它不能保证是无操作,但规范允许它成为无操作。当两个线程在同一个对象上同步时,规范只需要同步来建立两个线程之间的先发生关系,但实际上更容易实现一个JVM,其中对象的身份无关紧要。
我认为synchronized-Statement会导致缓存刷新
没有"缓存"在Java语言规范中。这个概念只存在于某些(即O.K.,几乎所有)硬件平台和JVM实现的细节中。