【问题标题】:Memory effects of synchronized keyword in JavaJava中同步关键字的记忆效应
【发布时间】:2020-05-25 03:02:45
【问题描述】:

这可能以前已经回答过,但由于问题的复杂性,我需要确认一下。所以我改写这个问题

问题1:当一个线程进入一个同步块时,内存屏障将包括所有触及的字段,而不仅仅是我同步对象的字段?因此,如果在一个同步块中修改了许多对象,那么就会在线程内存缓存之间移动大量内存。

Thread 1
object.field1 = "";
synchronized (lock) {
  farAwayObject.field1 = "";
  farAwayObject.evenFarther.field2 = "";
}

Thread 2. assuming thread ordering is correct
synchronized (lock) {
  //thread 2 guaranteed to see all fields above as ""
  //even object.field1 ?
}

问题 2:线程 1 中的 object.field1 = ""; 是否隐含地属于发生前关系?

我希望是,但可能不是。如果没有,是否有一个技巧可以在不将其放入同步块的情况下做到这一点?否则很难对程序进行推理 并且将所有内容都放在同步的 { } 下是不切实际的。

编辑:澄清:object.field1 不是易失性的,问题是“线程 2 是否会保证至少看到线程 1 的写入”。我的问题是关于内存可见性。为了论证,假设只有线程 1 写入非易失性 object.field1。

问题 2 可以改写为

“锁上的同步块是否会将之前所做的更改推送给其他线程在同一锁上同步?”

【问题讨论】:

  • 这两个问题的答案都是肯定的。
  • @shmosel 我了解到object.field1 不会成为happens-before 的一部分,Thread2 可能会或可能不会看到更新?

标签: java multithreading java-memory-model


【解决方案1】:

1) 当一个线程进入一个同步块时,内存屏障将包括所有触及的字段,而不仅仅是我同步的对象的字段?

正确。 (假设线程 1 和线程 2 在同一个锁上同步。)

因此,如果在同步块内修改了许多对象,那么线程内存缓存之间会发生大量内存移动。

可能,是的。但是,它(可能)不是缓存之间的移动。更有可能的是,它是从一个处理器的缓存到内存的移动,以及从内存到第二个处理器的缓存的移动。当然,这取决于硬件如何实现内存层次结构。

2) 是 object.field1 = "";在线程 1 中隐含地是发生前关系的一部分?

有一连串的happens-before关系

  1. object.field1 的写入发生在获取锁之前。
  2. 这发生在写入 farAwayObject 之前,依此类推。
  3. 这发生在线程 1 释放锁之前
  4. 这发生在线程 2 获取锁之前
  5. 这发生在线程 2 读取 object.field1 之前。

问题是,如果在lock 被线程 1 或其他线程获取之前,有对 object.field1 的干预写入会发生什么。在任何一种情况下,happens-before 链都不足以确保线程 2 看到线程 1 写入的值。

【讨论】:

  • 你能根据我的澄清澄清一下吗?假设没有干预写入。你会像 shmosel 一样回答是吗?
【解决方案2】:

当一个线程进入一个同步块时,内存屏障会 包括所有触及的领域,而不仅仅是我所接触的对象的领域 同步于

假设farAwayObjectevenFarther 的字段总是通过在应用程序周围的同一对象上获取lock 来修改和访问,所有线程将始终看到对farAwayObject 和@987654326 所做的更新@ since synchronized 强制执行 happens-before 条件。

//thread 2 guaranteed to see all fields above as ""

object.field1 在不知道它是如何声明的情况下不能这样说。假设 field1 是未标记为 volatile 的引用,则它不会是 happen before 关系的一部分,线程可能会看到它的陈旧值。

我希望是,但可能不是。如果没有,有什么诀窍可以做到这一点 不将其放入同步块中?

是的。将object.field1 标记为volatile


解决您的编辑:

问题2可以改写为

"将同步块上的锁推送之前所做的更改 在同一个锁上同步的其他线程看到了吗? "

AFAIK 答案是肯定的,前提是写入线程在读取线程之前获得锁。不幸的是,这是您通常无法保证的,这就是为什么需要将 object.field1 标记为 volatile 或将语句移到 synchronized 块内。

看看JSR 133,它谈到当线程退出 syncronized 块时缓存被刷新到主内存。这应该进一步澄清事情。


【讨论】:

  • 见我的澄清。如果我不想将 object.field1 设置为 volatile。执行同步(锁定)时,不是将thread1到object.field1的特定写入推送到主内存吗?如果不。这是否意味着 JVM 会标记在同步锁下所做的内存更改?以便只推动这些变化?
  • @Mordan 查看编辑。这会让事情更清楚吗?如果需要进一步澄清,请告诉我。
  • 您的链接显然回答了我的问题。是的。我放心了。 :) 我已经在网上搜索了几个星期,但从未找到它。很棒的链接!谢谢。
  • @Mordan 很高兴能提供帮助。我相信如果我在谈到其他任何事情之前谈到退出时的 synchronized 块刷新缓存,我的回答会更有意义。是,你的意思是其他线程将总是看到object.field的更新值?正如我的回答所解释的那样,这不是真的。
  • 是的。如果没有其他线程写入它,线程 2 将始终在 object.field 中看到“”,因为它在同一个锁上同步。您的链接提供了另一个宝石。 “写入 volatile 字段与监视器释放具有相同的记忆效应,而从 volatile 字段读取具有与监视器获取相同的记忆效应。实际上,因为新的内存模型对 volatile 字段访问的重新排序设置了更严格的限制其他字段访问,无论是否 volatile,线程 A 在写入 volatile 字段 f 时可见的任何内容在线程 B 读取 f 时都将变为可见。"
【解决方案3】:

内存屏障将包括所有触及的字段,而不仅仅是我同步的对象的字段?

是的。

是 object.field1 = "";在线程 1 中隐含地是发生前关系的一部分?

是的,即使它不是易失性的。


happens-before 顺序是偏序。

happens-before 顺序由 synchronizes-with 边和程序顺序的传递闭包给出。它必须是有效的偏序:自反、传递和反对称。

(JLS 17.4.7)

同步边缘之前的动作(即同步锁的释放)按程序顺序排序,因此与释放同步。传递性表示,由 acquire 在另一个线程中的同一锁排序的操作因此具有先发生顺序,即释放该锁和释放该锁之前的操作,无论是不是它在 synchronized 块的主体内。关于这一点要记住的重要一点是,排序发生在动作(即获取/释放锁)上,而不是像 synchronized 关键字的括号所暗示的块。括号表示获取/释放动作的位置以及一组动作不能交错的位置。

最后,请记住,happens-before 是“部分”顺序。意思是:

  1. 当操作碰巧按特定顺序(即释放/获取、写入/读取等)发生时,在强制内存一致性之前发生
  2. 发生之前取决于更强的保证,例如程序顺序以产生正确的功能。
  3. Happens before 并不能防止因非原子操作交错而产生的错误
  4. Happens before 是动作和强动作之间的传递关系。 (只要写入在块之前并且读取在块之后,您就可以将共享变量的读取放在 synchronized 块之外)。更强大的订购保证也会在订购之前发生,但它们也提供规范中描述的额外效果。

【讨论】:

    猜你喜欢
    • 2010-12-23
    • 1970-01-01
    • 2022-12-19
    • 1970-01-01
    • 1970-01-01
    • 2012-12-09
    • 2020-08-22
    • 2011-12-10
    • 1970-01-01
    相关资源
    最近更新 更多