小编典典
它可能有助于解释为什么首先存在这样的规则。
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简短”。但是我对此领域了解不多,因此您可能要为此提出一个单独的问题。
*:在实际的实现中,此差异取决于平台。
2020-11-13