让我们采取以下声明:
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组成?
答案 0 :(得分:6)
是的,{0, 1}
也是可能的。在这种情况下,Java内存模型不足以保证排序。这甚至不需要指令重新排序 - 如果你在x86或x86_64之外的任何东西上运行程序,这种情况无论如何都会发生。
要明确这一点,实际的CPU硬件将重新排序这些加载和存储,而不是它是否为x86。
答案 1 :(得分:2)
如果没有适当的同步,这确实是可能的。
Java语言规范在第17章中定义了多线程Java程序的语义。该章很难理解,但确实包含了可以依赖的官方规则。特别是writes:
在给定程序和该程序的执行跟踪的情况下,存储器模型描述执行跟踪是否是程序的合法执行。 Java编程语言内存模型的工作原理是检查执行跟踪中的每个读取,并根据某些规则检查该读取所观察到的写入是否有效。
内存模型描述了程序的可能行为。只要程序的所有结果执行产生可由内存模型预测的结果,实现就可以自由地生成它喜欢的任何代码。
为了粗略概述,内存模型定义happens-before relation任何重新排序必须为consistent with。建立在不同线程执行的操作之前发生的常用方法是synchronize这些操作,例如使用synchronized
块或写入或读取volatile变量。
如果没有这种同步,运行时将独立执行线程,允许重新排序当前线程无法观察到。
也就是说,如果你有可变的共享状态,你通常需要同步访问它的线程。
答案 2 :(得分:0)
是编译器和jvm(即时编译器)都可以进行指令重新排序。而且,硬件处理器可以做到。为防止不必要的重新排序,应使用memory barriers。