4

让我们采取以下陈述:

int d0, d1;
int[] ds = {0, 0};

现在一个线程具有以下说明:

d0++;
d1++;

而另一个线程有这个指令:

ds[1] = d1;
ds[0] = d0;

如果我们并行运行这些线程,显然有三种组合ds可以看起来像:{0, 0}、{1, 1} 和 {1, 0}。

现在最大的问题是:也可以有 {0, 1} 吗?编译器/JVM 可以简单地交换指令,因为它认为它们不相关吗?如果是,那么这种行为的“规则”到底是什么,这取决于编译器还是 JVM?

4

3 回答 3

6

是的,{0, 1}也是可以的。在这种情况下,Java 内存模型不足以保证排序。这甚至不需要指令重新排序——如果你在除 x86 或 x86_64 之外的任何东西上运行程序,这无论如何都会发生。

在这里要清楚的是,实际的 CPU 硬件将重新排序这些加载和存储,如果它是 x86 则不会。

请参阅Java 内存模型常见问题解答

于 2012-11-18T13:04:22.260 回答
2

在没有适当同步的情况下,这确实是可能的。

Java 语言规范在第 17 章中定义了多线程 Java 程序的语义。那一章很难理解,但它确实包含了可以依赖的官方规则。特别是,它写道

给定程序和该程序的执行轨迹,内存模型描述该执行轨迹是否是程序的合法执行。Java 编程语言内存模型的工作原理是检查执行跟踪中的每个读取,并根据某些规则检查该读取观察到的写入是否有效。

内存模型描述了程序的可能行为。一个实现可以自由地生成它喜欢的任何代码,只要程序的所有结果执行产生的结果可以由内存模型预测。

为了给出一个粗略的概述,内存模型定义了一个happens-before关系,任何重新排序都必须. 为不同线程执行的操作建立happens-before的常用方法是同步这些操作,例如使用synchronized块或写入或读取易失性变量。

在没有这种同步的情况下,运行时将独立执行线程,允许当前线程无法观察到的任何重新排序。

也就是说,如果你有可变的共享状态,你通常需要同步访问它的线程。

于 2012-11-18T13:33:55.830 回答
0

是的,编译器和 jvm(即时编译器)都可以进行指令重新排序。而且,硬件处理器可以做到。为防止不必要的重新排序, 应使用内存屏障。

于 2012-11-18T13:09:48.547 回答