Java并发中“程序顺序规则”的解释

程序顺序规则指出:“线程中的每个动作都发生在该线程中的每个动作之后,程序顺序之后”

我在另一个线程中读到一个 是

  • 读取和写入变量
  • 显示器的锁定和解锁
  • 用线程开始和加入

这是否意味着可以按顺序更改读取和写入,但不能通过第二行或第三行中指定的操作更改读取和写入操作的顺序?

2.“程序顺序”是什么意思?

举例说明将非常有帮助。

假设我有以下代码:

long tick = System.nanoTime(); //Line1: Note the time

//Block1: some code whose time I wish to measure goes here

long tock = System.nanoTime(); //Line2: Note the time

首先,它是一个单线程应用程序,可以使事情保持简单。编译器注意到它需要检查时间两次,并且还注意到一个代码块与周围的时间标记行没有依赖关系,因此它看到了重新组织代码的潜力,这可能导致Block1没有被定时调用所包围。在实际执行期间(例如,考虑此顺序Line1->

Line2->

Block1)。但是,作为程序员,我可以看到Line1,2和Block1之间的依赖关系。Line1应该紧接在Block1之前,Block1需要有限的时间才能完成,而Line2紧随其后。

所以我的问题是:我是否正确地测量块?

  • 如果是,则阻止编译器重新排列顺序。
  • 如果没有,(经过Enno的回答后认为是正确的),我可以采取什么措施来防止它。

PS:我从最近在SO中问到的另一个问题中窃取了此代码。

回答:

它可能有助于解释为什么首先存在这样的规则。

Java是一种过程语言。即,您告诉Java如何为您做某事。如果Java不按照您编写的顺序执行您的指令,则显然无法正常工作。例如,在下面的示例中,如果Java执行2->

1-> 3,则炖汤将被破坏。

1. Take lid off

2. Pour salt in

3. Cook for 3 hours

那么,为什么规则不简单地说“ Java按照您编写的顺序执行您编写的内容”?简而言之,因为Java很聪明。请看以下示例:

1. Take eggs out of the freezer

2. Take lid off

3. Take milk out of the freezer

4. Pour egg and milk in

5. Cook for 3 hours

如果Java像我一样,它将按顺序执行它。但是Java很聪明,足以理解它更有效,而且如果执行1-> 3-> 2-> 4->

5(如果您不必再走到冷冻室,并且不会改变配方)。

因此,规则“线程中的每个动作发生在该线程中的每个动作在程序顺序之后发生之前”是要说的:“在单个线程中,您的程序将 在完全执行中

运行您编写的顺序我们可能会更改幕后的顺序,但要确保这些顺序都不会改变输出。

到目前为止,一切都很好。为什么它在多个线程中不一样?在多线程编程中,Java不够聪明,无法自动执行。它将用于某些操作(例如,连接线程,启动线程,何时使用锁(监视器)等),但对于其他内容,则需要明确告诉它不要进行重新排序,否则会更改程序输出(例如volatile,字段上的标记,使用锁等)。

注意:

有关“先于关系”的快速附录。这是一种奇妙的说法,无论重新排序Java可能做什么,物料A都会在物料B之前发生。在我们后来出现的怪异例子中,“步骤1和3

发生在 步骤4“将鸡蛋和牛奶倒入”之前”。又例如,“步骤1和3不需要 事前发生的 关系,因为它们不以任何方式相互依赖”

首先,让我们确定“时间”在编程世界中的含义。在编程中,我们有“绝对时间”的概念(现在世界上的时间是什么?)和“相对时间”的概念(自x以来已经过去了多少时间?)。在理想的世界中,时间就是时间,但是除非我们内置了原子钟,否则绝对时间必须随时校正。另一方面,相对时间我们不希望进行更正,因为我们只对事件之间的差异感兴趣。

在Java中,System.currentTime()处理绝对时间并System.nanoTime()处理相对时间。这就是为什么nanoTime的Javadoc指出“此方法

只能用于测量经过的时间, 并且与系统或挂钟时间的任何其他概念无关”。

实际上,currentTimeMillis和nanoTime都是本机调用,因此编译器无法实际证明重新排序是否会影响正确性,这意味着它将不会对执行进行重新排序。

但是让我们想象一下,我们想编写一个编译器实现,它实际上可以查看本机代码并在合法的情况下对所有内容进行重新排序。当我们查看JLS时,它只告诉我们:“只要无法检测到任何东西,您都可以对其重新排序”。现在,作为编译器作者,我们必须确定重新排序是否会违反语义。对于相对时间(nanoTime),如果我们对执行重新排序,则显然是无用的(即违反了语义)。现在,如果我们重新排序绝对时间(currentTimeMillis),会违反语义吗?只要我们可以将世界时间的来源(例如系统时钟)与我们决定的任何时间(例如“

50ms”)*之间的时差限制为零,我就说不。对于以下示例:

long tick = System.currentTimeMillis();

result = compute();

long tock = System.currentTimeMillis();

print(result + ":" + tick - tock);

如果编译器可以证明它compute()花费的时间少于我们允许的最大系统时钟偏差,则按以下方式重新排序是合法的:

long tick = System.currentTimeMillis();

long tock = System.currentTimeMillis();

result = compute();

print(result + ":" + tick - tock);

由于这样做不会违反我们定义的规范,因此也不会违反语义。

您还询问为什么JLS中不包含此内容。我认为答案将是“保持JLS简短”。但是我对此领域了解不多,因此您可能要为此提出一个单独的问题。

*:在实际的实现中,此差异取决于平台。

以上是 Java并发中“程序顺序规则”的解释 的全部内容, 来源链接: utcz.com/qa/418612.html

回到顶部