Search code examples
volatile

why jmm reorder happened in this scenarios?


Lately, I've been thinking about the JMM;

As described in this cookbook jsr133-cookbook, normal-store then volatile-store can not reorder;

can reOrder? 2nd operation
1st operation Normal Load Normal Store Volatile load MonitorEnter Volatile store MonitorExit
Normal Load Normal Store No
Volatile load MonitorEnter No No No
Volatile store MonitorExit No No

now, I simulated a code scenario here;

    public static void main(String[] args) throws Exception {
        System.out.println(System.currentTimeMillis());
        for (int i = 0; i < 500000 * 8; i++) {
            final ReOrderClient client = new ReOrderClient();
            Thread t1 = new Thread(client::writer);

            Thread t2 = new Thread(client::reader);

            t1.start();
            t2.start();
        }

        System.out.println("the end");
    }

    private static class ReOrderClient {

        private boolean flag = false;
        private volatile int value = 0;

        private void writer() {
            flag = true;
            value = 2;
        }

        private void reader() {
            if (!flag && value == 2) {
                System.out.println("reOrder happened, client.value=" + value);
            }
        }
    }

my-CPU-info:

windows-10

Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
cpu: 4
L1-cahce:   256 KB
L2-cahce:   1.0 MB
L3-cahce:   6.0 MB

In actual testing, the code execution result:

reOrder happened, client.value=2
// s1:
private void writer() {
    flag = true;
    value = 2;
}

// s2:
private void writer() {
    value = 2;
    flag = true;
}

as I think only thread1 reorder, occur s2 scenario, then thread2 will have a chance to print the reorder result; but normal-write then volatile-write cause store-store-fence, why reorder happened?

in this question re-ordering in x86, I know that normal-write then volatile-write can not cause re-order in x86; so Just because the compiler Causes reorder;

why compiler reorder happened, please help me;


Solution

  • The problem is with the reader.

    private static class ReOrderClient {
    
        private boolean flag = false;
        private volatile int value = 0;
    
        private void writer() {
            flag = true;
            value = 2;
        }
    
        private void reader() {
            if (!flag && value == 2) {
                System.out.println("reOrder happened, client.value=" + value);
            }
        }
    }
    

    If we look at the reader, we can simplify it to:

      r1=flag  (plain load)
      r2=value (volatile load)
      
    

    There is nothing that prevents an earlier plain load to be reordered with a later volatile load.

    On a JMM level; because of the wrong ordering of the 2 loads, there is a data race since a happens before edge is missing between the store and the load of flag.

    You need to flip them around and then you will get a [LoadLoad] between the 2 loads.

       private void reader() {
            if (value == 2 && !flag) {
                System.out.println("reOrder happened, client.flag=" + flag);
            }
        }
    

    This gives:

      r1=value (volatile load)
      [LoadLoad]
      r2=flag  (plain load)
     
    

    And now the reordering should not happen. On a JMM level we have introduced a happens before edge between the store and the load of 'flag'