【问题标题】:Why is this Java program containing two volatile writes data race free?为什么这个包含两个 volatile 写入数据的 Java 程序没有竞争?
【发布时间】:2023-06-23 10:20:01
【问题描述】:

考虑以下 Java 程序:

static volatile int shared;

public static void main(final String[] args) {
  final Runnable r = () -> {
    shared = 1;
  };

  new Thread(r).start();
  new Thread(r).start();
}

因为shared 被标记为volatile,我想说这个程序没有数据竞争。但是,如何基于 JLS(例如版本 11)来激发这一点?

第 17 章告诉我们:

当一个程序包含两个冲突的访问(第 17.4.1 节)时,这些访问没有按发生前的关系排序,则称为包含数据竞争。

我认为这是 JLS 提供的数据竞争的定义。然后我们有第 17.4.1 节告诉我们:

如果至少有一次访问是写入,则对同一变量的两次访问(读取或写入)称为冲突。

好的,因为我们有两次写入shared,所以我们在这里有冲突的访问。现在我们必须在两个写入之间建立起之前发生的关系,否则我们就会有比赛。然而,我没有设法找到为什么我们在这里有这种关系。第 17 章告诉我:

如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。

同一章告诉我:

对 volatile 变量 v(第 8.3.1.4 节)的写入与任何线程对 v 的所有后续读取同步(其中“后续”根据同步顺序定义)。

但在将写入关联到易失性变量之前,我还没有发现任何事情发生。这是为什么呢?

【问题讨论】:

  • volitile 变量的访问从不受到数据竞争的影响。这是根据“数据竞争”术语的定义。参见例如给定问题:*.com/questions/7805192/….
  • @Tsyvarev:当然,但我不明白它是如何“根据'数据竞争'的定义”——我们从哪里得到两个写入之间的发生前关系?还是有其他原因导致数据竞争定义不适用于我们的情况?
  • 嗯,我的意思是在引用的短语“当一个程序包含两个冲突的访问......它被称为包含数据竞争”。他们的意思是访问“正常”变量。此短语不适用于 volatile 变量的访问。我现在没有这本书,但我很确定标准中明确说明了这种例外情况。
  • @Tsyvarev:我还没有看到任何这样的“明确说明”的例外。 “书”可在线获得,您能否提供一个链接到说明此例外的相关部分?

标签: language-lawyer volatile java-memory-model happens-before data-race


【解决方案1】:

volatile 变量的访问从不受到数据竞争的影响。

从 JLS 看不是很明显,而是在短语中

(第 17.4.1 节)如果至少有一次访问是写入,则对同一变量的两次访问(读取或写入)称为冲突。

术语“读取”和“写入”不是通用术语,但在下一节“17.4.2 操作”中描述为对非易失性变量的访问:

读取(正常或非易失性)。读取变量。

写入(正常或非易失性)。写一个变量。

在该部分中,对 volatile 变量的访问被归类为 同步操作 的一部分,具有不同的术语“易失性读取”和“易失性写入”:

易失性读取。变量的易失性读取。

易失性写入。变量的易失性写入。

因为只有(非易失性)读取和写入可能会发生冲突,所以只有那些访问可能包含数据竞争易失性读取和写入永远不会发生冲突,并且永远不会包含数据竞争

【讨论】:

  • 哦,这非常不明显......特别是考虑到他们在定义它们之前使用了这些术语。无论如何谢谢!
  • 是的,我需要重新阅读“17.4”部分 3 次才能找到这种关系。即使我已经确定它存在......