为什么 `synchronized (new Object()) {}` 是空操作?

Why is `synchronized (new Object()) {}` a no-op?

在下面的代码中:

class A {
    private int number;

    public void a() {
        number = 5;
    }

    public void b() {
        while(number == 0) {
            // ...
        }
    }
}

如果方法 b 被调用,然后启动一个新线程触发方法 a,那么方法 b 不能保证永远看到 number 的变化,因此 b 可能永远不会终止。

当然我们可以numbervolatile来解决这个问题。但是出于学术原因,我们假设 volatile 不是一个选项:

JSR-133 FAQs告诉我们:

After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory.

这听起来好像我只需要 ab 就可以进入和退出任何 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 的更改,因此最终也会终止。

但是常见问题解答也明确指出:

Another implication is that the following pattern, which some people use to force a memory barrier, doesn't work:

synchronized (new Object()) {}

This is actually a no-op, and your compiler can remove it entirely, because the compiler knows that no other thread will synchronize on the same monitor. You have to set up a happens-before relationship for one thread to see the results of another.

现在这很令人困惑。我认为同步语句会导致缓存刷新。它肯定不能以主内存中的更改只能由在同一监视器上同步的线程看到的方式将缓存刷新到主内存,特别是因为对于基本上做同样事情的 volatile 我们甚至不需要监视器,还是我弄错了?那么为什么这是一个空操作并且不会导致 b 保证终止?

the following pattern, which some people use to force a memory barrier, doesn't work:

不能保证它是空操作,但规范允许它是空操作。当两个线程在同一个对象上同步时,规范只要求同步在两个线程之间建立先行发生关系,但实际上实现对象身份无关紧要的 JVM 会更容易。

I thought that the synchronized-Statement will cause caches to flush

Java 语言规范中没有 "cache"。这是一个只存在于某些(嗯,O.K,几乎所有)硬件平台和 JVM 实现的细节中的概念。

常见问题解答不是此事的权威; JLS 是。 17.4.4 部分指定了同步关系,它提供给 happens-before 关系 (17.4.5)。相关要点是:

  • An unlock action on monitor m synchronizes-with all subsequent lock actions on m (where "subsequent" is defined according to the synchronization order).

因为这里的m是对new Object()的引用,而且它从来没有被存储或发布到任何其他线程,我们可以肯定没有其他线程会获取释放此块中的锁后,锁定 m。此外,由于 m 是一个新对象,我们可以确定之前没有对其解锁的操作。因此,我们可以确定没有任何动作与此动作正式同步。

从技术上讲,您甚至不需要执行完全缓存刷新即可符合 JLS 规范;它超出了 JLS 的要求。 典型 实现可以做到这一点,因为这是硬件允许您做的最简单的事情,但可以这么说 "above and beyond"。在 escape analysis 告诉优化编译器我们需要更少的情况下,编译器可以执行更少。在您的示例中,逃逸分析可以告诉编译器该操作无效(由于上述推理)并且可以完全优化。