Search code examples
javacompiler-optimizationvolatilejls

Is it true that java volatile accesses cannot be reordered?


Note
By saying that a memory access can (or cannot) be reordered I meand that it can be reordered either by the compiler when emitting byte code byte or by the JIT when emitting machine code or by the CPU when executing out of order (eventually requiring barriers to prevent this) with respect to any other memory access.


If often read that accesses to volatile variables cannot be reordered due to the Happens-Before relationship (HBR).

I found that an HBR exists between every two consecutive (in program order) actions of a given thread and yet they can be reordered.

Also a volatile access HB only with accesses on the same variable/field.

What I thinks makes the volatile not reorderable is this

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

If there are others threads a reordering of the variables will becomes visible as in this simple example

        volatile int a, b;            

Thread 1                Thread 2

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

So is not the HBR itself that prevent the ordering but the fact that volatile extends this relationship with other threads, the presence of other threads is the element that prevent reordering.
If the compiler could prove that a reordering of a volatile variable would not change the program semantic it could reorder it even if there is an HBR.

If a volatile variable is never accessed by other threads than its accesses could be reordered

      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

I think c=3 could very well be reordered before a=1, this quote from the specs confirm this

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.

So I made these simple java programs

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;
   }
}

In both cases both fields are marked as volatile and accessed with putstatic. Since these are all the information the JIT has1, the machine code would be identical, thus the vtest1 accesses will not be optimized2.

My question

Are volatile accesses really never reordered by specification or they could be3, but this is never done in practice?
If volatile accesses can never be reordered, what parts of the specs say so? and would this means that all volatile accesses are executed and seen in program order by the CPUs?

1Or the JIT can known that a field will never be accessed by other thread? If yes, how?.
2Memory barriers will be present for example.
3For example if no other threads are involved.


Solution

  • I'm going to be using notation from JLS §17.4.5.

    In your second example, (if you'll excuse my loose notation) you have

    Thread 1 ordering:
    hb(a = 1, b = 2)
    hb(b = 2, c = 3)

    Volatile guarantees:
    hb(b = 2, b != 2)
    hb(a = 1, access a for print)

    Thread 2 ordering:
    hb(while(b != 2);, print(a))

    and we have (emphasis mine)

    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.

    There is no happens-before relationship between c=3 and Thread 2. The implementation is free to reorder c=3 to its heart's content.