1

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;

farui yu
  • 21
  • 4

3 Answers3

0

That document you are looking at, says:

For a compiler writer, the JMM mainly consists of rules disallowing reorderings...

Me, you, and 999_999 out of 1_000_000 people reading that document, aren't compiler writers. That book is not a reference implementation of the JMM in the JVM; JLS chapter 17 is.

JVM, as long as it does not break the JLS can do these kind of transformations under the hood, it does not violate the specification of the JLS in any way. The StoreStore fence that you are talking about, is simply a non-existing concept in the language specification.

The absolute most famous contra-example is lock coarsening, see this for more details.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • tks,bro;but,I'm still confused; If the barrier(fence) is simply a non-existing concept , how is volatile-write visibility guaranteed? Or, similar to lock coarsening, compiler optimization removes a barrier – farui yu Apr 12 '21 at 04:40
  • @faruiyu the visibility guarantee of `volatile` is there, only when you use it correctly. I mean in reverse of what you are doing. _If_ you observer `value` to be `2`, you have the guarantee to see `flag` to be `true`, not in reverse. – Eugene Apr 12 '21 at 11:27
0

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'

pveentjer
  • 10,545
  • 3
  • 23
  • 40
  • tks;Your answer is very useful;I'll try to summarize the problem in my own way;I want you to check it; – farui yu Apr 13 '21 at 01:24
-1

@pveentjer tks for help! Your answer was great. It made me think;

Interpretation of the problem

farui yu
  • 21
  • 4
  • I'm not sure how to interpret 'sequence' of events. If you mean the order of execution, then a possible allowed sequence of executions would be: normal_store, volatile_store, volatile_load, normal load. The problem is that the normal load can jump in front of the volatile load.. – pveentjer Apr 13 '21 at 03:29
  • If you mean the happens before edges, then there should be a happens before edge between normal write and volatile write, volatile write and volatile read. But in the above problem there is no happens before edge between the normal write and the normal load which is the cause of your problem. – pveentjer Apr 13 '21 at 03:31
  • On a fences level: the stores are correctly ordered. But you have a problem with the loads; in your code, there is no [LoadLoad] fence placed between the 2 loads and this can lead to the reordering. You need to flip the 2 loads and then you get a [LoadLoad] fence as I described in my answer. So if you first do a volatile load and then a plain load, then these 2 loads can't be reordered. – pveentjer Apr 13 '21 at 03:33
  • Before jumping into fences, I would first make sure that you have the proper understanding of JMM. Thinking in fences is not a replacement of the JMM; trust me.. been there... done that.. – pveentjer Apr 13 '21 at 03:40
  • What makes memory models so difficult (and hence interesting) is that you need to juggle balls of different levels at the same time; you have the memory model at the language level, potential implementations using fences and memory models on the isa and microarchitectural level. Luckily most people only need to deal with the memory model at the language level. – pveentjer Apr 13 '21 at 04:03