【问题标题】:Java Memory Model - Surprising BehaviorsJava 内存模型 - 令人惊讶的行为
【发布时间】:2021-10-11 09:42:47
【问题描述】:

我正在阅读 JSR-133 中的 Java 内存模型,但我无法理解这种行为是如何被接受的:

谁能解释一下?

【问题讨论】:

  • 那个,以及 x 在线程之间共享并且操作不是原子的事实。
  • 你的两个答案都不正确。
  • 是的,我注意到了。但后来我不明白这个问题,8.1 明确描述了所需的、有点晦涩但可以想象的架构
  • 严格按照JLS,这称为数据竞争。对于数据竞赛来说,一切皆有可能,如果你也能展示你在哪里看到这个例子,那将会很有帮助

标签: java java-memory-model


【解决方案1】:

CPU 必须确保的唯一事情是在线程中写入 X 不会影响随后分配到其关联的 RX 内存位置。它没有说明它从哪里获得要写入的值。

所以, 在线程 1 中,CPU 说
“哦,我需要读取 X”,因此开始读取操作。
然后它说
“我需要写入 X”,然后 QUEUES 写入队列中的值

线程 2 做同样的事情。
“哦,我需要阅读 X”并开始阅读。
“我需要写入 X”,并将写入排队。

现在我们有两个等待读取和两个排队写入。

如果 CPU 架构表明一个核心上的读取可能会询问另一个核心的写入队列,那么两个核心可以相互读取对方对 X 的未完成写入。因此,您可以将两个值跨核心拉取,最终分配给 RX该线程的内存位置。

当您在指令流中放置内存屏障时,它可以防止这种过度急切的排队写入读取。

【讨论】:

  • r1r2 不是“内存位置”,或者至少不是任何其他线程引用的内存位置。在 CPU 术语中,它们是寄存器,这就是为什么它们具有像 r1 这样的名称。半相关:preshing.com/20120515/memory-reordering-caught-in-the-act 但这是关于 StoreLoad 重新排序,而不是 LoadStore 重新排序,这是创建所询问的条件所必需的。 (除了这是对同一内存位置的重新排序操作,这是不寻常的。)
【解决方案2】:

8.1 内存模型允许的令人惊讶的行为

图 12 显示了一个小而有趣的示例。行为 r1 == 2 并且 r2 == 1 是合法行为,尽管可能很难看到 它是如何发生的。编译器不会重新排序中的语句 每个线程;此代码决不能导致 r1 == 1 或 r2 == 2。 然而,行为 r1 == 2 和 r2 == 1 可能被 a 早期执行写入的处理器架构,但在某种程度上 它们对在它们之前的本地读取不可见 节目顺序。这种行为虽然令人惊讶,但 Java 允许 记忆模型。为了在内存模型中得到这个结果,我们同时提交 先写后读。

不知何故,CPU 决定在读取 x 之前写入 x。这个例子所说的只是,这是有效的行为,或多或少,这是一个被接受为有效行为的异常。

Intel Itanium CPU 可能会产生这种行为。

所以而不是:

//Thread 1
int x = 0;
int r1 = x;
x = 1;

//Thread 2
int x = 0;
int r2 = x;
x = 2;

发生这种情况:

//Thread 1
int x = 0;
x = 2; //from Thread 2
int r1 = x;

//Thread 2
int x = 0;
x = 1; //from Thread 1
int r2 = x;

这是完全有效的。 (可接受的例外。)

【讨论】:

  • 有问题的法律行为是r1 == 2r2 == 1。您的示例显示 r1 == 1r2 == 2 而“编译器不会重新排序每个线程中的语句;[...]”。
  • 谢谢,我更正了。
  • @paladin 什么允许 CPU 将 [r1 = x, x = 5] 重新排列为 [x = 5, r1 = x]?它会破坏所有程序吗?谢谢
  • 它与“权利”无关,而是与硬件限制有关。某些 CPU 必须以某种方式处理某些代码,这就是存在此异常的原因。我同意你的观点,如果不处理这个异常会产生软件错误,这就是为什么它被 API 记录并定义为有效的原因。所以程序员必须注意这种废话;-) 我很确定通常的主流 CPU 不会受到这种行为的影响。
猜你喜欢
  • 2019-10-05
  • 1970-01-01
  • 1970-01-01
  • 2021-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-27
  • 2015-10-19
相关资源
最近更新 更多