当前位置: 首页 > 面试题库 >

Java中的指令重新排序和先发生后关系

毋澄邈
2023-03-14
问题内容

在《 Java Concurrency InPractice》一书中,有几次告诉我们可以通过编译器,运行时JVM甚至处理器来重新排序程序的指令。因此,我们应该假定执行的程序不会以与源代码中指定的顺序完全相同的顺序执行其指令。

但是,上一章讨论的Java内存模型提供了一系列先 发生后 规则的清单,这些规则指示JVM保留哪些指令顺序。这些规则中的第一个是:

  • “程序顺序规则。线程中的每个动作先于该线程中的每个动作发生,然后再依次出现在程序顺序中。”

我相信“程序顺序”是指源代码。

我的问题是 :假设遵循此规则,我想知道实际上可以重新排序什么指令。

“动作”的定义如下:

Java内存模型是根据操作来指定的,这些操作包括对变量的读取和写入,监视器的锁定和解锁以及与线程的启动和连接。JMM定义了程序中所有动作之前发生的部分顺序。为了确保执行操作B的线程可以看到操作A的结果(无论A和B是否发生在不同的线程中),在A和B之间的关系之前必须发生一个事件。在两个之间进行排序之前,必须先发生一个事件操作,JVM可以根据需要随意重新排序。

提到的其他订购规则是:

  • 监视锁定规则。监视器锁的解锁发生在该监视器锁上的每个后续锁之前。
  • 易变变量规则。在每次后续读取同一字段之前,都会对易失字段进行写操作。
  • 线程启动规则。在启动线程中的每个操作之前,都会在线程上调用Thread.start。
  • 线程终止规则。通过成功从Thread.join返回或通过Thread.isAlive返回false,线程中的任何操作都会在其他线程检测到该线程已终止之前发生。
  • 中断规则。一个线程在另一个线程上调用中断发生在被中断的线程检测到该中断之前(通过引发InterruptedException或调用isInterrupted或被中断)。
  • 终结器规则。对象的构造函数的结束发生在该对象的终结器的开始之前。
  • 传递性。如果A发生在B之前,并且B发生在C之前,那么A发生在C之前。

问题答案:

程序顺序规则的关键点是: 在线程中

想象一下这个简单的程序(所有变量最初为0):

T1:

x = 5;
y = 6;

T2:

if (y == 6) System.out.println(x);

从T1的角度来看,执行必​​须与在x(程序顺序)之后分配的y一致。但是,从T2的角度来看,情况并非一定如此,T2可能会显示0。

实际上,允许T1首先分配y,因为这2个分配是独立的,并且交换它们不会影响T1的执行。

通过正确的同步,T2将始终打印5或什么都不打印。

编辑

您似乎在误解程序顺序的含义。程序顺序规则可以归结为:

如果xy是同一个线程的动作和x来之前y在程序顺序,然后hb(x, y)(即x 之前发生 y)。

__在JMM中, before-before 具有非常特定的含义。特别是,它
意味着y=6必须之后x=5在T1从挂钟透视。这仅意味着T1执行的操作顺序必须 该顺序 一致 。您还可以参考JLS
17.4.5

应当注意的是,两个动作之间存在先发生关系 并不一定意味着在实现中它们必须以该顺序发生 。如果重新排序产生的结果与合法执行相符,则不合法。

在我上面给出的示例中,您将同意从T1的角度来看(即在单线程程序中)x=5;y=6;与一致,y=6;x=5;因为您不读取值。在T1中,保证下一行的语句可以看到这2个动作,而与执行顺序无关。



 类似资料:
  • 问题内容: 我正在阅读此博客文章。 作者正在谈论在多线程环境中打破in 。 有了: 变成: 作者说,我引用: “我在这里所做的是添加一个附加读取: 哈希的第二次读取,在返回之前 。听起来很奇怪,而且不太可能发生,第一次读取可以返回正确计算出的哈希值,内存模型允许第二次读取返回0!这是允许的,因为该模型允许对操作进行广泛的重新排序。第二次读取实际上可以在代码中移动,以便处理器在第一次读取之前进行处理

  • 问题内容: 我只是碰到一篇文章,声称我从未听过,也找不到其他地方。声称是从另一个线程的角度来看,可以根据构造函数内部的指令对构造函数返回的值的分配进行重新排序。换句话说,声称是在下面的代码中,另一个线程可以读取其中未设置的非空值。 这是真的? 编辑: 我认为从线程执行的角度来看,可以保证的分配与的分配具有先发生后关系。但是,这两个值都可能缓存在寄存器中,并且可能未按照最初写入的顺序将它们刷新到主存

  • 变量res的值应等于3。但是当我打开优化时,编译器错误地重新排列了指令,并且res包含一些垃圾。一些可能的重新排序示例: 这是编译器中的错误吗?还是不允许像这样访问结构数据成员? 编辑: 我刚刚意识到之前的代码实际上有效,抱歉。但这不起作用: 当编译时不知道变量i时,编译器会错误地重新排序指令。

  • 我最近遇到了一个问题。我看了一段关于数据依赖性的视频。讲师说,通过对指令重新排序,可以解决数据依赖性问题。写入后读取危险示例: 在这种情况下,第二条指令有两个档位。通过重新排序指令: 我们消除了先读后写的危险。从技术上讲,这是正确的。但是,我想知道,第二种方法是否在逻辑上是错误的,并且会引入错误的结果? 如果我错了,请纠正我,因为我是新手。

  • 我有一个关于Java中代码重新排序和竞争条件的问题。 假设我有以下代码,有2个或多个线程同时执行: JVM是否可能以错误的顺序执行此操作?例如,以下重新排序是否可能?: 还是保证锁不会被重新排序?

  • Java指令重排序不影响单线程下执行结果。那么比如main线程里有 System.out.println("1"); System.out.println("2"); 这两句话会重排导致输出21吗?这里似乎没有数据依赖? 无