Java中的StoreStore memory barrier是否禁止读写重排序?

Does StoreStore memory barrier in Java forbid the read-write reordering?

现在我们有

Load A
StoreStore
Store B

有没有可能实际执行顺序如下

StoreStore
Store B
Load A

如果可能的话,如何解释似乎违反The Java volatile Happens-Before Guarantee的情况。

据我所知,使用以下JMM内存屏障添加策略实现volatile语义

insert a StoreStore before volatile variable write operation
insert a StoreLoad after volatile variable write operation
insert a LoadLoad after volatile variable read operation
insert a LoadStore after volatile variable read operation

现在如果我们有两个 java 线程,如下所示

线程 1

Load A
StoreStore
Store volatile B

线程 2

Load volatile B
Load C

根据"The Java volatile Happens-Before Guarantee",当Load volatile BStore volatile B之后,Load A应该发生在Load C之前,但是如果Load A可以重订到"after Store volatile B",如何保证Load A is before Load C

从技术上讲,Java 语言没有记忆障碍。 Java 内存模型是根据 happens before 关系指定的;详情见以下:

  • Behavior of memory barrier in Java

您讨论的术语来自The JSR-133 Cookbook for Compiler Writers。正如文档所说,它是编写实现 Java 内存模型的编译器的人员的指南。它解释了 JMM 的 implications,显然无意成为官方规范。 JLS是规范。

JSR-133 Cookbook 中有关内存屏障的部分根据它们限制特定加载和存储序列的方式对其进行了分类。对于 StoreStore 个障碍,它表示:

The sequence: Store1; StoreStore; Store2 ensures that Store1's data are visible to other processors (i.e., flushed to memory) before the data associated with Store2 and all subsequent store instructions. In general, StoreStore barriers are needed on processors that do not otherwise guarantee strict ordering of flushes from write buffers and/or caches to other processors or main memory.

如您所见,StoreStore 屏障仅限制 store 操作的行为。

在您的示例中,您有一个 load,后跟一个 storeStoreStore 屏障的语义与 load 操作无关。因此,您建议的重新排序是允许的。

这只是回答您问题的更新部分。

首先,您提供的示例不是 Java 代码。因此我们不能对其应用 JMM 推理。 (只是为了让我们清楚这一点。)

如果您想了解 Java 代码的行为方式,忘记内存障碍。 Java 内存模型告诉您需要做的所有事情,以便内存读写具有有保证的行为。以及推理(正确)行为所需知道的一切。所以:

  • 编写您的 Java 代码
  • 分析代码以确保在线程需要读取另一个线程写入的值的所有情况下,发生在 链之前。
  • 将(正确的)Java代码编译成机器指令的问题留给编译器。

查看您示例中的伪指令序列,它们没有多大意义。我认为真正的 Java 编译器在编译真正的 Java 代码时不会(在内部)使用这样的障碍。相反,我认为在每次易失性写入之后和每次易失性读取之前都会有一个 StoreLoad 内存屏障。

让我们考虑一些真实的 Java 代码片段:

public int a;
public volatile int b;

// thread "one"
{
  a = 1;
  b = 2;
}

// thread "two"
{ 
  if (b == 2) {
      print(a);
  }
}

现在假设线程 "two" 中的代码在线程 "one" 之后执行,就会有这样一个 happens-before 链:

  • a = 1 发生在 b = 2
  • 之前
  • b = 2 发生在 b == 2
  • 之前
  • b == 2 发生在 print(a)
  • 之前

除非涉及其他代码,否则 happens-before 链意味着线程 "two" 将打印“1”。

注:

  1. 无需考虑编译器在编译代码时使用的内存屏障。
  2. 障碍是特定于实现的并且是编译器内部的。
  3. 如果您查看本机代码,您将看不到内存障碍本身。您将看到具有所需语义的本机指令,以确保存在(隐藏的)内存屏障。