【问题标题】:Java - how does Lock guarantee happens-before relationship?Java - Lock 如何保证发生前的关系?
【发布时间】:2017-04-22 09:06:56
【问题描述】:

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

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

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

我了解,基于 Javadoc,这相当于 synchronized 块。我很难看到这实际上是如何在较低级别执行的。

Lock 的状态是 volatile,在调用 lock() 时它会执行 volatile 读取,然后在释放时执行 volatile 写入。写入一个对象的状态如何确保doLotsOfWork任何指令(可能涉及许多不同的对象)不会乱序执行?

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

【问题讨论】:

  • 我在 Lock/ReentrantLock 实现中看不到任何 volatile。你可以在grepcode.com/file_/repository.grepcode.com/java/root/jdk/… 上查看 ReentraltLock 的实现
  • 通过它的实现。它没有定义。仅需要。
  • AbstractQueuedSYnchronizer 的状态是一个易变的变量
  • 无法保证doLotsOfWork() 中的指令不会在它们之间重新排序。保证不会使用lock() 之前和unlock() 之后的说明对它们进行重新排序。

标签: java multithreading concurrency locking volatile


【解决方案1】:

好吧,如果我理解正确,那么您的答案是here。 volatile 写入和读取会引入 内存屏障LoadLoadLoadStore禁止重新排序。在 CPU 级别,这被转换为实际的内存屏障,例如 mfencelfence(CPU 也通过其他一些机制强制非重新排序,因此您可能还会在机器代码中看到其他内容)。

这是一个小例子:

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 之外的其他方法。

【讨论】:

    【解决方案2】:

    来自 Oracle 的documentation

    每次后续读取都会写入volatile 字段发生之前 同一个领域。 volatile 字段的写入和读取具有相似的 内存一致性效果作为进入和退出监视器,但确实 需要互斥锁定。

    Java 并发实践 更清楚地说明了这一点:

    volatile 变量的可见性影响超出了值 volatile 变量本身。当 线程 A 写入 volatile 变量,随后 线程 B 读取相同的变量, 在写入之前对 A 可见的 所有 变量的值 在读取volatile 后,volatile 变量对 B 可见 变量。

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

    【讨论】:

    • 我明白了。这是 JMM 的高级描述。我想知道对 volatile 变量的写入如何导致没有重新排序可能会搞砸,编译器无法提前知道(有状态变量是 volatile)
    猜你喜欢
    • 2017-07-06
    • 1970-01-01
    • 2021-02-06
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    • 2013-04-21
    相关资源
    最近更新 更多