wait() 和 notify() JMM 语义

wait() and notify() JMM semantics

我有一个非常特殊的问题,我找不到答案。

正如我们所知,在 synchronized 块的入口处,线程会重新读取其范围内的所有共享(非本地)变量。某些底层架构的示例:如果线程 A 更新 RAM 中的对象状态,线程 B 进入同步块将看到更改。 从 synchronized 块退出时会发生类似的事情:线程将其范围内的所有内容刷新到 RAM,以便其他线程可以看到它。 这些是基本的 JVM 可见性保证,并且存在发生前规则来强制执行它。

但是,从语义上讲,使用 wait()notify() 的代码是否也执行所有这些操作还不是很清楚:毕竟它没有明确地进入或离开 synchronized 块.

问题是这些:

  1. JVM 是否确保 wait() 入口处其他线程的更改的可见性?
  2. JVM 是否确保在 wait() 休假时其他线程中所做更改的可见性?
  3. 该线程是否确保 notify() 上其他线程的更改可见性?

As we know, on entrance in synchronized blocks a thread performs a re-read of all the variables in it's scope; that is, if thread A updates the object's state in RAM thread B entering a synchronized block will see the changes.

第一部分不正确。不会重读所有变量。局部变量肯定不会。它们不需要重读,因为它们对其他线程永远不可见。

正确的说法是编译器将确保线程 A 退出块之前写入的 shared 变量对线程 B after 它已进入块。假设 A 和 B 在同一个互斥对象上同步,并且假设 A(或其他线程)在此期间没有覆盖它们。

notifynotifyAll 没有明确的内存语义。但是,wait 将导致互斥锁被释放并(通常)重新获得。释放和重新获取与其他线程有关联 happens-before 关系。


Could you, please, elaborate on exact semantics associated with releasing and acquiring a lock? Are they one and same with entering the synchronized block?

假设我们只有两个线程 A 和 B,以及一个互斥锁 L。假设我们从没有持有互斥锁的线程开始。

还要记住waitnotify只能被持有锁的线程调用

  1. 线程 A 获取 L。
  2. 线程 A 调用 L.wait()
    • 线程A放入L的等待队列,释放L。
  3. 线程 B 获取 L。
  4. 线程 B 调用 L.notify()
    • 线程 A 被移到等待获取 L 的线程队列中。
  5. 线程 B 释放 L。
  6. 线程A重新获取锁,L.wait()调用returns.

这里重要的 happens-before 边在 2 和 3 之间,然后在 5 和 6 之间。

如果涉及多个线程,您可以通过链接 happens-before 关系来分析行为。但是在释放互斥锁的线程和下一个获取它的线程之间只有一个直接的 HB ......无论如何。


那么,您的问题的答案是:

1) & 2) 是的,假设另一个线程正确使用 synchronized。 3) 否。可见点是调用 notify().

的线程释放互斥锁时

请注意,内存屏障、刷新等是实现细节。事实上,编译器可以自由地以任何它想要的方式实现 happens-before 的语义。如果不需要,包括(假设)优化内存刷新。

最好 (IMO) 忽略这些实现细节, 考虑 happens-before 关系。

有关 happens-before 的更多信息,请阅读:

  • How to understand happens-before consistent
  • Brian Goetz 等人"Java concurrency in action" 的内存模型章节
  • JVM 规范第 17.4 章