为什么`synchronized(new Object()){}`a no-op?

时间:2016-05-10 15:08:21

标签: java multithreading java-memory-model

在以下代码中:

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块后,我们释放了监视器,具有将缓存刷新到主内存的效果,因此该线程所做的写操作对其他线程可见。在我们进入同步块之前,我们获取了监视器,具有使本地处理器缓存无效的效果,以便从主存储器重新加载变量。

这听起来我只需要ab进入和退出任何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通过保证终止?

2 个答案:

答案 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实现的细节中。