【发布时间】:2016-09-05 15:51:44
【问题描述】:
在以下代码中:
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 告诉我们:
退出同步块后,我们释放监视器,它具有将缓存刷新到主内存的效果,因此该线程所做的写入可以对其他线程可见。在我们进入同步块之前,我们获取了监视器,它具有使本地处理器缓存无效的效果,以便从主内存重新加载变量。
这听起来我只需要a 和b 进入和退出任何synchronized-Block,无论他们使用什么监视器。更准确地说,它听起来像这样......:
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()) {}这实际上是一个空操作,您的编译器可以完全删除它, 因为编译器知道没有其他线程会同步 同一个显示器。您必须为 一个线程查看另一个线程的结果。
现在这令人困惑。我认为同步语句会导致缓存刷新。它肯定不能将缓存刷新到主内存,因为主内存中的更改只能由在同一监视器上同步的线程看到,特别是因为对于基本上做同样事情的 volatile 我们甚至不需要监视器,还是我弄错了?那么为什么这是一个无操作并且不会导致b 被保证终止呢?
【问题讨论】:
-
引用未保存,因此没有其他线程能够等待该引用。你想保护什么?
-
这个问题完美地说明了我敦促人们不要试图解释或理解语言语义的原因,这些内容可能存在也可能不存在,例如虚构的“本地处理器缓存”。
-
@DavidSchwartz 没错,实际上“缓存刷新”这个短语,尤其是在“主内存”这个短语附近时,可能会对实际发生的事情产生很大的误导。高速缓存一致性协议通常可以确保内存一致性,而无需实际到达慢速主内存。而且,如果您放弃 JLS 和 JMM 并尝试对底层架构进行推理,则需要考虑诸如某些同步机制不能确保全局内存一致性(由 IRIW 演示)之类的事实。所以,就像 yshavit 说的,坚持使用 JLS 才是正确的选择。
标签: java multithreading java-memory-model