关于可见性及时性的易失性的详细语义 [英] Detailed semantics of volatile regarding timeliness of visibility

查看:79
本文介绍了关于可见性及时性的易失性的详细语义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑一个 volatile int sharedVar 。我们知道JLS给了我们以下保证:

Consider a volatile int sharedVar. We know that the JLS gives us the following guarantees:


  1. 写作线程的每个动作 w 在将值 i 写入 sharedVar 之前,按程序顺序发生在<之前写入操作;

  2. w i c $ c> 成功读取 i 之前发生 sharedVar 由阅读主题 r ;

  3. 成功阅读 i 来自 sharedVar 由阅读线程 r 发生在之前按程序顺序 r 的所有后续操作。

  1. every action of a writing thread w preceding its write of value i to sharedVar in program order happens-before the write action;
  2. the write of value i by w happens-before the successful read of i from sharedVar by a reading thread r;
  3. the successful read of i from sharedVar by the reading thread r happens-before all subsequent actions of r in program order.

然而,仍有没有挂钟时间保证,读取线程将观察值 i 。只需永远不会的实现让阅读线程看到该值仍然符合此合约。

However, there is still no wall-clock time guarantee given as to when the reading thread will observe the value i. An implementation that simply never lets the reading thread see that value still complies with this contract.

我已经考虑了一段时间了,我看不到任何漏洞,但我认为必须有。请指出我的推理中的漏洞。

I have thought about this for a while and I can't see any loopholes, but I assume there must be. Please, point out the loophole in my reasoning.

推荐答案

事实证明,答案和随后的讨论只能巩固我原来的推理。我现在有一些证明的方式:

Turns out that the answers and the ensuing discussions only consolidated my original reasoning. I now have something in the way of a proof:


  1. 在写入线程开始执行之前,读取线程完全执行的情况;

  2. 注意此特定运行创建的同步顺序;

  3. 现在在挂钟时间内移动线程,以便它们并行执行,但保持相同的同步顺序

  1. take the case where the reading thread executes in full before the writing thread starts executing;
  2. note the synchronization order that this particular run created;
  3. now shift the threads in wall-clock time so they execute in parallel, but maintain the same synchronization order.

由于Java内存模型没有提及挂钟时间,因此不会有障碍。现在,您有两个与读取线程并行执行的线程,观察写入线程没有执行任何操作。 QED。

Since the Java Memory Model makes no reference to wall-clock time, there will be no obstructions to this. You now have two threads executing in parallel with the reading thread observing no actions done by the writing thread. QED.

为了使这个发现极为尖锐和真实,考虑以下程序:

To make this finding maximally poignant and real, consider the following program:

static volatile int sharedVar;

public static void main(String[] args) throws Exception {
  final long startTime = System.currentTimeMillis();
  final long[] aTimes = new long[5], bTimes = new long[5];
  final Thread
    a = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        sharedVar = 1;
        aTimes[i] = System.currentTimeMillis()-startTime;
        briefPause();
      }
    }},
    b = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        bTimes[i] = sharedVar == 0?
            System.currentTimeMillis()-startTime : -1;
        briefPause();
      }
    }};
  a.start(); b.start();
  a.join(); b.join();
  System.out.println("Thread A wrote 1 at: " + Arrays.toString(aTimes));
  System.out.println("Thread B read 0 at: " + Arrays.toString(bTimes));
}
static void briefPause() {
  try { Thread.sleep(3); }
  catch (InterruptedException e) {throw new RuntimeException(e);}
}

就JLS而言,这是一个合法的输出:

As far as JLS is concerned, this is a legal output:

Thread A wrote 1 at: [0, 2, 5, 7, 9]
Thread B read 0 at: [0, 2, 5, 7, 9]

请注意,我不依赖 currentTimeMillis 的任何故障报告。报道的时间是真实的。但是,实现确实选择只在读取线程的所有操作之后才能使写入线程的所有操作都可见。

Note that I don't rely on any malfunctioning reports by currentTimeMillis. The times reported are real. The implementation did choose, however, to make all actions of the writing thread visible only after all the actions of the reading thread.

现在@StephenC认为,很多人会同意他的意见,发生在之前,即使没有明确提及它,仍然暗示时间排序。因此,我提出了我的第二个程序,它可以证明这可能的确切程度。

Now @StephenC argues, and many would agree with him, that happens-before, even though not explicitly mentioning it, still implies a time ordering. Therefore I present my second program that demonstrates the exact extent to which this may be so.

public static void main(String[] args) throws Exception {
  final long startTime = System.currentTimeMillis();
  final long[] aTimes = new long[5], bTimes = new long[5];
  final int[] aVals = new int[5], bVals = new int[5];
  final Thread
    a = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        aVals[i] = sharedVar++;
        aTimes[i] = System.currentTimeMillis()-startTime;
        briefPause();
      }
    }},
    b = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        bVals[i] = sharedVar++;
        bTimes[i] = System.currentTimeMillis()-startTime;
        briefPause();
      }
    }};
  a.start(); b.start();
  a.join(); b.join();
  System.out.format("Thread A read %s at %s\n",
      Arrays.toString(aVals), Arrays.toString(aTimes));
  System.out.format("Thread B read %s at %s\n",
      Arrays.toString(bVals), Arrays.toString(bTimes));
}

为了帮助理解代码,这将是一个典型的现实世界结果:

Just to help understanding the code, this would be a typical, real-world result:

Thread A read [0, 2, 3, 6, 8] at [1, 4, 8, 11, 14]
Thread B read [1, 2, 4, 5, 7] at [1, 4, 8, 11, 14]

另一方面,你永远不会期望看到这样的东西,但是它仍然是JMM标准的合法性

On the other hand, you'd never expect to see anything like this, but it is still legit by the standards of the JMM:

Thread A read [0, 1, 2, 3, 4] at [1, 4, 8, 11, 14]
Thread B read [5, 6, 7, 8, 9] at [1, 4, 8, 11, 14]

JVM实际上必须预测线程A将在时间14写入什么才能知道什么让线程B在时间1读取。这种可能性甚至可行性是非常可疑。

The JVM would actually have to predict what Thread A will write at time 14 in order to know what to let the Thread B read at time 1. The plausibility and even feasibility of this is quite dubious.

从这里我们可以定义以下,JVM实现可以采取的现实自由:

From this we can define the following, realistic liberty that a JVM implementation can take:

条款发布获取JLS§17.4.4

此规则的一个原因是只有写入且永远不会读取任何内容的帖子可以无限期推迟,而不会违反发生之前的关系。

A corrollary to this rule is that the actions of a thread which only writes and never reads anything can be postponed indefinitely without violating the happens-before relationship.

volatile 修饰符实际上是两个截然不同的概念:

The volatile modifier is actually about two distinct concepts:


  1. 硬保证上的操作将尊重发生前订购;

  2. 软承诺运行时为及时发布写入所做的最大努力。

  1. The hard guarantee that actions on it will respect the happens-before ordering;
  2. The soft promise of a runtime's best effort towards a timely publishing of writes.

注意关键点2. JLS没有以任何方式指明它,它只是出于一般期望而产生的。显然,违背承诺的实施仍然是合规的。随着时间的推移,当我们转向大规模并行架构时,这种承诺可能确实非常灵活。因此,我希望将来保证与承诺的合并将证明是不够的:根据要求,我们需要一个没有另一个,一个具有不同的风味,或任何数量的其他组合。

Note the point 2. is not specified by the JLS in any way, it just kind of arises by general expectation. An implementation that breaks the promise is still compliant, obviously. With time, as we move to massively parallel architectures, that promise may indeed prove to be quite flexible. Therefore I expect that in the future the conflation of the guarantee with the promise will prove to be insufficient: depending on requirement, we'll need one without the other, one with a different flavor of the other, or any number of other combinations.

这篇关于关于可见性及时性的易失性的详细语义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆