java 易失性访问不能重新排序是真的吗?

Is it true that java volatile accesses cannot be reordered?

备注
通过说内存访问可以(或不能)重新排序,我的意思是它可以 在发出字节码 byte 时由编译器重新排序,或在发出时由 JIT 重新排序 机器代码或 CPU 在乱序执行时(最终需要障碍来防止这种情况)相对于任何其他内存访问。


如果经常阅读,由于 Happens-Before 关系 (HBR),无法重新排序对 volatile 变量的访问。

我发现在每两个连续的(按程序顺序)动作之间存在一个 HBR 一个给定的线程,但它们可以重新排序。

也是一个易失性访问 HB,只能访问相同的 variable/field。

我认为 volatile 不可重新订购的是这个

A write to a volatile field (§8.3.1.4) happens-before every subsequent read [of any thread] of that field.

如果有其他线程变量的重新排序将变得可见 简单的例子

        volatile int a, b;            

Thread 1                Thread 2

a = 1;                  while (b != 2); 
b = 2;                  print(a);       //a must be 1

所以不是 HBR 本身阻止了排序,而是 volatile 扩展了与其他线程的这种关系,其他线程的存在是阻止重新排序的因素.
如果编译器可以证明 volatile 变量的重新排序不会改变 程序语义它可以重新排序即使有 HBR.

如果一个 volatile 变量除了它的访问之外永远不会被其他线程访问 可以重新排序

      volatile int a, b, c;            

Thread 1                Thread 2

a = 1;                  while (b != 2); 
b = 2;                  print(a);       //a must be 1
c = 3;                  //c never accessed by Thread 2

我认为 c=3 可以在 a=1 之前重新排序,这来自规范 确认这一点

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

所以我制作了这些简单的 java 程序

public class vtest1 {
   public static volatile int DO_ACTION, CHOOSE_ACTION;

   public static void main(String[] args) {

      CHOOSE_ACTION = 34;
      DO_ACTION = 1;
   }
}

public class vtest2 {
   public static volatile int DO_ACTION,  CHOOSE_ACTION;

   public static void main(String[] args) {

      (new Thread(){

         public void run() {

             while (DO_ACTION != 1);
             System.out.println(CHOOSE_ACTION);
         }
      }).start();

      CHOOSE_ACTION = 34;
      DO_ACTION = 1;
   }
}

在这两种情况下,两个字段都标记为 volatile 并使用 putstatic 访问。 由于这些是 JIT 拥有的所有信息1,机器代码将是相同的, 因此 vtest1 访问不会被优化2.

我的问题

易失性访问是否真的从未按规范重新排序,或者它们可能是3,但实际上从未这样做过?
如果永远无法对易失性访问进行重新排序,那么规范的哪些部分是这样说的?这是否意味着 CPUs?

按程序顺序执行和查看所有易失性访问

1或者JIT可以知道一个字段永远不会被其他线程访问?如果是,如何?
2例如内存障碍。
3例如,如果不涉及其他线程。

JLS 所说的(来自 JLS-8.3.1.4. volatile Fields)部分是

The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes.

A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable (§17.4).

这意味着访问可能被重新排序,但任何重新排序的结果必须最终一致(当被另一个线程访问时)与原始订单。单线程应用程序中的字段 不需要 锁定(来自 volatilesynchronization)。

我将使用来自 JLS §17.4.5 的符号。

在你的第二个例子中,(如果你能原谅我的松散符号)你有

线程 1 排序:
hb(a = 1, b = 2)
hb(b = 2, c = 3)

不稳定的保证:
hb(b = 2, b != 2)
hb(a = 1, 为 print 访问 a)

线程 2 排序:
hb(while(b != 2);, print(a))

我们有(强调我的)

More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

c=3 和线程 2 之间没有发生之前的关系。实现可以自由地重新排序 c=3 到它的核心内容。

来自17.4. Memory Model of JLS

The memory model describes possible behaviors of a program. An implementation is free to produce any code it likes, as long as all resulting executions of a program produce a result that can be predicted by the memory model.
This provides a great deal of freedom for the implementor to perform a myriad of code transformations, including the reordering of actions and removal of unnecessary synchronization.

Java 内存模型为正确同步的程序提供顺序一致性 (SC)。简单来说,SC 意味着如果某个程序的所有可能执行都可以通过不同的执行来解释,其中所有内存操作都按某种顺序执行,并且该顺序与每个线程的程序顺序 (PO) 一致,那么该程序与这些顺序执行一致;所以它是顺序一致的(因此得名)。

这实际上意味着 JIT/CPU/memory 子系统可以根据需要重新排序易失性写入和读取,只要存在也可以解释实际执行结果的顺序执行。所以实际的执行并不那么重要。

如果我们看下面的例子:

  volatile int a, b, c;            

  Thread 1                Thread 2

  a = 1;                  while (c != 1); 
  b = 1;                  print(b); 
  c = 1;               

a=1b=2之间有一个happens before关系(PO),在c=2c=3之间有一个happens before关系(PO)和一个happens在关系 c=1c!=0 之前(可变变量规则)和在 c!=0print(b) (PO) 之间的关系之前发生。

由于happens before关系是传递的,所以a=1print(b)之间存在happens before关系。所以从这个意义上说,它不能重新排序。但是,没有人证明发生了重新排序,所以仍然可以重新排序。