首页 > 解决方案 > volatile 是否提供其他正常存储并加载发生前的关系?

问题描述

我有一个关于 volatile 发生前规则的问题,同样的问题也符合监控规则。根据易失性规则,易失性写入发生在任何后续读取之前。

我有以下易失性写入和正常后续读取的示例。据我所知,这种易失性写入应该有一个 StoreStore 内存屏障,它将普通存储刷新到内存中,以便其他进程可以看到它(根据 DougLea 关于 JSR-133 内存模型的食谱)。

所以我的问题是:是否还有一个额外的发生前规则,即正常存储的操作 1 也发生在正常加载的后续操作 2 之前。

int a;
volatile int x;


public void action1(){
    a = 100;  --- normal store
    x = 123;  ---volatile store
}

public void action2{
    int k = a; ---normal load
}

标签: java

解决方案


是否还有一个额外的先发生规则,即正常存储的操作 1 也发生在正常加载的后续操作 2 之前?

不,它没有。

发生前发生在易失性写入和后续易失性读取之间。

在您的示例中,缺少易失性读取,因此没有将发生之前的关系链接到非易失性读取。因此,您的程序在内存可见性方面格式不正确。分配给的值k可能不是100, 在某些硬件等上。

要解决此问题,您需要执行以下操作:

int a;
volatile int x;

public void action1() {
    a = 100;  --- normal store
    x = 123;  ---volatile store
}

public void action2() {
    int x = x; ---volatile load
    int k = a; ---normal load
}

我还有一个问题,为什么 volatile 保证以下正常负载使用内存而不是缓存?(java规范没有解释底层,只说明规则,所以我不太了解背后的机制。

Java 规范故意不谈论硬件。相反,它指定了格式良好的 Java 程序的先决条件。如果程序满足这些先决条件,则可见性属性得到保证。 如何满足它们是编译器编写者的问题。

JMM 规范的一个结果是,在具有缓存和多处理器的硬件上,最明显和最有效的实现方法是进行缓存刷新等。但这是编译器编写者的关注点……而不是您的关注点。

您(Java 程序员)不需要了解缓存、内存屏障等。您只需要了解发生前发生的规则。但是,如果您想根据 JSR 133 食谱来理解事物,那么需要牢记以下几点:

  1. 食谱不是权威的,也不完整的。说的这么清楚。

  2. Cookbook 仅与格式良好的程序的行为直接相关。如果所需的先发生链不存在,则可能会丢失必要的障碍,并且其他事情和所有赌注都将失败。

  3. 实际的 Java 实现不一定会按照 Cookbook ... umm ... 推荐的方式做事。

请注意,对于我的(更正)版本的示例,食谱说两个负载之间会有/应该有一个 LoadLoad 屏障。


推荐阅读