【问题标题】:Happens-before with different monitorsHappens-before 使用不同的显示器
【发布时间】:2017-03-07 06:20:24
【问题描述】:

在这个网站上的某个地方(我不记得这个问题)有人声称,对于不同的监视器和不同的易失性变量,发生之前发生的关系,即

// Thread T1
synchronized(O1)
{
}

// Thread T2
synchronized(O2)
{
}

如果线程 T1 在线程 T1 离开 synchronized(O1){} 块后进入 synchronized(O2){} 块,T2 将看到 T1 所做的所有更改。解释是当线程离开同步块(或写入 volatile 变量)时,它会将其缓存刷新到内存中。当一个线程进入同步块(或读取 volatile 变量)时,它会丢弃它的缓存并从内存中读取。是真的吗?

【问题讨论】:

  • 不,不是。在某些硬件上可能会出现这种情况,但 Java 内存模型无法保证。一般来说,在处理多线程 Java 代码时,根据硬件实现细节(如内存和缓存刷新)进行推理并不是一个好主意。
  • 从理论上讲,我认为这个问题是一个谬论。您不能说一个线程在没有 happens-before 关系的情况下“在”另一个线程“之后”执行某些操作,这不是为单独的监视器定义的。更实际地,特定 JVM 可能会在同步块结束时刷新其缓存这一事实并不意味着它是有保证的行为。
  • 你可能在这里谈论我的回答:stackoverflow.com/a/21583879/179850
  • @Gray 谢谢,这确实是我在谈论的问题。我的错 - 你实际上说相反的事情发生之前 relashionship。
  • 嗨弗拉基米尔,看看:stackoverflow.com/questions/32845755/…

标签: java multithreading happens-before


【解决方案1】:

使用synchronizedvolatile 所产生的记忆效应就是——记忆效应。观察到内存“从缓存中刷新”的事实与synchronizedvolatile 没有直接关系。 Happens-before 关系仅在操作是由于JLS 17.4.5 中指定的操作或由于位于 happens-before 链中(即搭载) 与这些变量。如果您对不同的变量执行两个 synchronizedvolatile 操作,则存在 NO happens-before 关系。

记忆效应是由 happens-before 排序产生的,但 happens-before 绝不是记忆效应的结果。

【讨论】:

  • 说得好。对于不同的监视器,从来没有发生之前的保证,但是如果按照帖子描述的顺序发生,则有内存同步保证。我要做的唯一更正是 JVM 实现的一种方式发生在之前是通过内存影响。记忆影响不会“发生在排序之前”。
  • 这个答案不准确。它是更强的 synchronizes-with 关系,happens-before 关系包括但不限于,关于你所谓的记忆效应更强:“同步顺序是对所有同步操作的总顺序执行。” 因此,你所说的记忆效应是同步排序的结果,而不是发生之前的关系。例如,在同一线程中对非易失性变量的两次访问之间存在happens-before关系,但根本不存在synchronizes-with关系。
  • 我。 “这个答案不准确。” jmm 甚至不需要“记忆效应”来产生所需的动作,即使对于同步排序也是如此。内存刷新、对高速缓存和主内存的读/写以及 membar 是硬件/实现特定的细节,仅在 java 级别用于描述类比或提供过于简单的解释以提供硬件可能执行的示例。提问者想知道内存刷新在不同线程中是如何工作的,您的评论与问题本身和我的答案几乎没有关系。
【解决方案2】:

在 volatile 变量的关系为真之前发生。如果在保持关系之前发生同步块,以防您锁定同一对象。在您的情况下,同步用于不同的对象,这意味着 2 个不同的线程可以进入两个不同的同步块。在这种情况下,java 内存模型不保证发生在关系之前。

【讨论】:

  • volatile 变量的规则是一样的。保证仅适用于相同的变量。
  • 但实际情况是 JVM 要支持内存屏障,它在 CPU 缓存层这样做,不是volatile 字段或同步监视器层。有关更多信息,请参阅我的答案:stackoverflow.com/a/21583879/179850
【解决方案3】:

不同的监视器之间没有同步关系。

获取锁与后续执行之间存在happens-before关系,执行与后续锁释放之间存在happens-before关系。

如果一个线程在另一个线程离开同步块之后进入同步块,则在前一个线程上执行同步块和在后一个线程上执行同步块之间可能存在先发生关系。

您只能确定同步块是否或何时不与 synchronizes-with 关系同时运行。没有这种关系,你就会有数据竞争。

如果您显示的代码是线程之间的全部,则没有同步关系,因此没有发生之前的关系。答案的其余部分显示了代码比问题中提供的代码更多的情况。

建立同步关系的一件事是加入一个线程,例如等待前一个线程完成。另一种是启动一个线程,比如前一个线程完成后启动后一个线程。

// In a thread
t1.start();
t1.join();
t2.start();

// Or in thread 1
synchronized (O1) {
    // actions in thread 1
}
t2.start();

// Or in thread 2
t1.join();
synchronized (O2) {
    // actions in thread 2
}

或者如果后一个线程正在获取前一个线程拥有的外部锁(最终被释放):

// in thread 1
synchronized (outerLock) {
    synchronized (O1) {
        // actions in thread 1
    }
}

// in thread 2
synchronized (outerLock) {
    synchronized (O2) {
        // actions in thread 2
    }
}

或者如果后一个线程正在等待前一个线程可验证地通知的锁(最终):

boolean done;

// in thread 1
synchronized (O1) {
    // actions in thread 1
}
synchronized (commonLock) {
    done = true;
    commonLock.notify();
}

// in thread 2
synchronized (commonLock) {
    while (!done) {
        commonLock.wait();
    }
}
synchronized (O2) {
    // actions in thread 2
}

或者如果后一个线程读取前一个线程写入的 volatile 变量:

volatile boolean done;

// in thread 1
synchronized (O1) {
    // actions in thread 1
}
done = true;

// in thread 2
while (!done) {
    Thread.yield();
}
synchronized (O2) {
    // actions in thread 2
}

在这种特殊情况下,线程 2 仅在线程 1 写入 done 后,如果它观察到变化,它才确定它已读取 done

实际上,所有这些情况都有 synchronizes-with 关系,因此也就是happens-before 关系。同步动作是保证总顺序的动作,所以并不是所有的happens-before关系都在线程之间。

Java Language Specification 8th Edition §17,您可以阅读有关同步操作的信息:

同步顺序是执行的所有同步操作的总顺序。

您可以阅读以下关于发生前关系的内容:

应该注意的是,两个动作之间存在 happens-before 关系并不一定意味着它们必须在实现中以该顺序发生。如果重新排序产生与合法执行一致的结果,则不是非法的。

这允许在单个线程中,将非易失性读取重新排序至最近一次获取之后,并将非易失性写入重新排序至下一个版本之前。

所以,如果你从中取出一些东西:

  • synchronizes-with 关系是关于多线程的

  • 并非所有的happens-before关系都是关于多线程的

【讨论】:

  • 您混淆了某个线程操作中围绕内存屏障的排序限制以及 2 个不同线程之间的发生前操作。同样,happens-before 与同步锁保证的线程之间的执行顺序无关,而是与内存发布有关。我只是不明白最后一段。
  • 感谢您的反馈,我的回答更清楚了。
猜你喜欢
  • 2018-04-13
  • 1970-01-01
  • 2019-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-05
相关资源
最近更新 更多