1

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

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

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

这些问题是:

  1. JVM 是否确保在wait()入口处对其他线程的更改可见?
  2. JVM 是否确保在其他线程中所做的更改可见wait()
  3. 线程是否确保对其他线程的更改可见 notify()
4

1 回答 1

6

众所周知,在进入同步块时,线程会重新读取其范围内的所有变量;也就是说,如果线程 A 在 RAM 中更新对象的状态,线程 B 进入同步块将看到更改。

第一部分是不正确的。不会重读所有变量。局部变量肯定不会。它们不需要重新阅读,因为它们永远不会被另一个线程看到。

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

没有与 anotify或关联的显式内存语义notifyAll。但是, await将导致互斥锁被释放并(通常)重新获取。释放和重新获取与一些其他线程相关联的happens-before关系。


请您详细说明与释放和获取锁相关的确切语义吗?它们与进入同步块是否相同?

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

还要记住,wait并且notify只能由持有锁的线程调用

  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()调用返回。

这里重要的发生前边缘在 2 和 3 之间,然后在 5 和 6 之间。

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


因此,您的问题的答案是:

1) & 2) 是的,假设另一个线程使用synchronized正确。3)不。可见点是互斥锁被调用的线程释放的时候notify()


请注意,内存屏障、刷新等是实现细节。事实上,编译器可以随意实现happens-before的语义。如果没有必要,包括(假设地)优化掉内存刷新。

最好(IMO)忽略这些实现细节,考虑发生前的关系。

有关发生之前的更多信息,请阅读:

于 2020-05-02T16:32:58.117 回答