在以下代码中:
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常见问题告诉我们:
退出同步块后,我们释放监视器,具有将缓存刷新到主内存的效果,因此由该线程编写的写入可以对其他线程可见。在进入同步块之前,我们获取监视器,的效果是使本地处理器缓存无效,以便从主内存中重新加载变量。
这听起来就像我只需要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()) {}
}
}
}...would消除了问题,并将保证b将看到对a的更改,从而也将最终终止。
然而,“常见问题”也明确指出:
另一个含义是,下面的模式--一些人用来强制设置记忆障碍的模式--不起作用: 同步(新对象()) {} 这实际上是一个无操作,您的编译器可以完全删除它,因为编译器知道在同一个监视器上不会有其他线程同步。您必须在一个线程看到另一个线程的结果之前设置一个发生关系。
这就让人困惑了。我认为同步语句会导致缓存刷新。主内存中的变化只能由同步在同一监视器上的线程看到,这肯定不能将缓存刷新到主存,特别是对于易失性线程,它基本上做的是我们甚至不需要监视器的事情,还是我弄错了?那么,为什么这是一个不操作,并没有导致b终止的保证?
发布于 2016-05-10 15:16:48
FAQ不是这件事的权威;JLS是的。节17.4.4指定在关系发生之前(17.4.5)与关系同步。有关的重点是:
由于这里的m是对new Object()的引用,而且它从未存储或发布到任何其他线程,所以我们可以确保在释放这个块中的锁之后,没有其他线程会获得对m的锁。此外,由于m是一个新的对象,所以我们可以肯定,在它上面没有任何先前解锁的操作。因此,我们可以确保没有任何操作与此操作正式同步。
从技术上讲,您甚至不需要进行完全的缓存刷新就可以达到JLS规范;这比JLS所要求的要多。一个典型的实现可以做到这一点,因为这是硬件允许您做的最简单的事情,但可以说,它正在“超越”。如果逃逸分析告诉优化编译器我们需要的更少,编译器可以执行得更少。在您的示例中,转义分析可以告诉编译器操作没有效果(由于上面的推理),并且可以完全优化。
发布于 2016-05-10 15:13:44
有些人使用下面的模式来强制设置记忆屏障,但这种模式不起作用:
它不一定是不操作,但规范允许它是不操作。当两个线程在同一个对象上同步时,规范只需要建立一个发生在两个线程之间的关系之前的同步,但是在对象的身份不重要的情况下,实现JVM实际上要容易一些。
我认为同步语句会导致缓存刷新。
Java语言规范中没有“缓存”。这个概念只存在于一些硬件平台和JVM实现的细节中。
https://stackoverflow.com/questions/37142411
复制相似问题