Java - Lock 如何保证 happens-before 关系?

Java - how does Lock guarantee happens-before relationship?

让我们考虑以下 Java 中的标准同步:

public class Job {
   private Lock lock = new ReentrantLock();

   public void work() {
       lock.lock();
       try {
           doLotsOfWork();
       } finally {
           lock.unlock();
       }
   }
}

我理解,根据 Javadoc,这等同于 synchronized 块。我正在努力了解这实际上是如何在较低级别上执行的。

Lock 有一个易变的状态,在调用 lock() 时它会执行易失性读取,然后在释放时它会执行易失性写入。写入一个对象的状态如何确保 doLotsOfWork 的指令 的 none 不会被执行,这可能会触及许多不同的对象出问题了?

或者想象一下,doLotsOfWork 实际上被替换为 1000 多行代码。显然,编译器无法提前知道锁内某处存在 volatile,因此它需要停止对指令进行重新排序。那么,如何保证 lock/unlock 的 happens-before,即使它是围绕一个单独对象的易失性状态构建的?

来自 Oracle 的 documentation:

A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

Java 并发实践 说得更清楚:

The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When a thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.

应用于 ReentrantLock 这意味着在 lock.unlock() 之前执行的所有操作(在您的情况下为 doLotsOfWork() )将保证 之后发生打电话给 lock.lock()doLotsOfWork() 中的指令仍然可以相互重新排序。这里唯一可以保证的是,任何随后将调用 lock.lock() 获取锁的线程将在调用 lock.unlock().

之前看到 doLotsOfWork() 中完成的所有更改

好吧,如果我没理解错的话,你的答案就是here。易失性写入和读取引入 内存障碍 LoadLoadLoadStore禁止 重新排序。在 CPU 级别,这被转换为实际的内存障碍,如 mfencelfence (CPU 也通过其他一些机制强制不重新排序,所以你可能会看到一些东西else 在机器代码中也是如此)。

这是一个小例子:

i = 42;
j = 53;
[StoreStore]
[LoadStore]
x = 1; // volatile store

ij 之间的分配可以重新排序,但 它们不能 x=1 或者换句话说 i and j 不能低于x。

同样适用于 volatile reads

对于您的示例,doLotsOfWork 中的每个操作都可以根据编译器的喜好重新排序,但不能使用 lock operations 重新排序。

另外当你说编译器不能知道有一个volatile read/write时,你有点错了。它必须知道,否则就没有其他方法可以防止这些重新排序。

此外,最后一点:自 jdk-8 以来,您可以通过 Unsafe 强制执行非重新排序,它提供了除 volatile 之外的方法。