-1

I have next sample of code:

class Shared {
    int x;
    int y;

    void increment() {
        x++;
        y++;
    }

    void check() {
        if (y > x) {
            System.out.println("Ooops! y > x");
        }
    }
}

Looks clear? But main problem happens here when i am trying to increment and check in two threads:

Shared shared = new Shared();

        Thread writer = new Thread(() -> {
            for (int i = 0; i < N; i++) {
                shared.increment();
            }
        });

        Thread reader = new Thread(() -> {
            for (int i = 0; i < N; i++) {
                shared.check();
            }
        });

writer.start();
reader.start();

You can notice data race (instructions reordering in some cases?):

1. x++;
2. y++;

And now, i know about special VM flags, which can help me to print JIT compiler logs (-XX:+PrintCompilation).

...
    120  181       3       Shared::increment (21 bytes)
    120  182       3       Shared::check (20 bytes)
    120  183       4       Shared::increment (21 bytes)
    120  184       4       Shared::check (20 bytes)
    121  181       3       Shared::increment (21 bytes)   made not entrant
    121  182       3       Shared::check (20 bytes)   made not entrant
    121  185     n 0       java.lang.invoke.MethodHandle::linkToStatic(L)L (native)   (static)
    121  184       4       Shared::check (20 bytes)   made not entrant
    122  186       3       Shared::check (20 bytes)
    122  187     n 0       java.lang.Object::clone (native)   
    122  188       4       Shared::check (20 bytes)
    122  189 %     3       Main::lambda$main$0 @ 2 (19 bytes)
    122  190       3       Main::lambda$main$0 (19 bytes)
    123  186       3       Shared::check (20 bytes)   made not entrant
...

OK, now i can see how compilation of increment method was processed:

    120  181       3       Shared::increment (21 bytes)
    120  183       4       Shared::increment (21 bytes)
    121  181       3       Shared::increment (21 bytes)   made not entrant

Do i understand correct, that reordering here is due of tiered compilation? Because increment() - hot method, JIT compiler profiles this information and use C2 server compiler. And, as i think, reorder some instructions in such way, but in some cases optimization happens(made not entrant). Or it's wrong?

Also, there are some another logs for compilation:

    138  182       2       Shared::increment (21 bytes)
    138  184       4       Shared::increment (21 bytes)
    138  182       2       Shared::increment (21 bytes)   made not entrant
SlandShow
  • 443
  • 3
  • 13
  • *"You can notice instructions reordering"* I can see no indication of reordering in that question. Did you perhaps mean to say that your program prints `Ooops! y > x`? If so, why do you believe that is an effect of code reordering, and not memory cache refreshing between the two threads? – Andreas Mar 29 '20 at 09:53
  • If you want to *see* if there is code reordering, you need to look at the code JIT generates, so read this: [How to see JIT-compiled code in JVM?](https://stackoverflow.com/q/1503479/5221149) – Andreas Mar 29 '20 at 09:57
  • @Andreas >f so, why do you believe that is an effect of code reordering, and not memory cache refreshing between the two threads? Because it's looks like instructions reordering. – SlandShow Mar 29 '20 at 09:58
  • It's a lot more likely that you're seeing race conditions between the two threads. But, only one way to be sure. Do your **research** instead of guessing, i.e. look at the JIT-compiled code. – Andreas Mar 29 '20 at 10:00
  • @Andreas my question - is not a statement. I'am just researching. But from my point of you, here is not race condition, it's data race. And difference is big, because if we are talking about data race - it's looks like that CPU reorder some instructions. – SlandShow Mar 29 '20 at 10:03
  • I enhanced your `Ooops!` print statement to show the values. On first run I got `Ooops! y > x (59759 > 60415)`. On another run I got `Ooops! y > x (47619 > 48071) Ooops! y > x (479509 > 479506) ...` – Andreas Mar 29 '20 at 10:03
  • @Andreas And? What u want to say? – SlandShow Mar 29 '20 at 10:04
  • I know how to fix such incident. But still, what u want now? Are you trying to prove your case? I still think that it's because of data race. And yes, what about bytecode compilation log? – SlandShow Mar 29 '20 at 10:13
  • 1
    Changed the enhanced print to store `x` and `y` in local variables, so the printed values are what is actually compared by the `if` statement. I get e.g. `2623 > 2622, 19040 > 19035, 22985 > 22984, 31192 > 29296, ...` is a single run. Those are differences of 1, 5, 1, and 1896, and that is a symptom of **data races, not code reordering**. You're right, it's a data race, not a race condition, I misspoke on that, but it sure isn't code reordering either. --- My point: Your claim *"You can notice instructions reordering"* is unproven, and unlikely, and is based on a false assumption. – Andreas Mar 29 '20 at 10:18
  • I'm sorry, but *where* can we see that? Your question doesn't show any *bytecode*, and it doesn't show any *deoptimization*. What we do see is that JIT *compiles* the code (presumably optimizing it too but we don't see that since we don't see the code it compiled into), the exact opposite of what you just said. – Andreas Mar 29 '20 at 10:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/210522/discussion-between-slandshow-and-andreas). – SlandShow Mar 29 '20 at 10:30

1 Answers1

2

This is not related to Tiered Compilation. The problem also happens without it. Let JVM compile just one method check and see how it looks in C2-compiled code:

java -XX:-TieredCompilation \
     -XX:CompileCommand=compileonly,Shared::check \
     -XX:CompileCommand=print,Shared::check \
     Shared

The output is

    0x00000000031a4160: mov     dword ptr [rsp+0ffffffffffffa000h],eax
    0x00000000031a4167: push    rbp
    0x00000000031a4168: sub     rsp,20h           ;*synchronization entry
                                                  ; - Shared::check@-1 (line 11)

(1) 0x00000000031a416c: mov     r10d,dword ptr [rdx+0ch]
                                                  ;*getfield x
                                                  ; - Shared::check@5 (line 11)

(2) 0x00000000031a4170: mov     r8d,dword ptr [rdx+10h]  ;*getfield y
                                                  ; - Shared::check@1 (line 11)

    0x00000000031a4174: cmp     r8d,r10d
    0x00000000031a4177: jnle    31a4185h          ;*if_icmple
                                                  ; - Shared::check@8 (line 11)

    0x00000000031a4179: add     rsp,20h
    0x00000000031a417d: pop     rbp
    0x00000000031a417e: test    dword ptr [1020000h],eax
                                                  ;   {poll_return}
    0x00000000031a4184: ret

As you can see, x is loaded first (line 1), and y is loaded after (line 2). Between these lines another thread may increase y an aribrary number of times, thus making y seem greater than x.

In this particular case you've guessed about reordering of loads with respect to the original program order (in the bytecode getfield y goes before getfield x). However, as @Andreas mentioned, this is not the only reason why the program may break. Even if JIT compiler emits load(y) before load(x), depending on the CPU architecture, it may happen that the first load gets a newer value, while the second load gets an older one, and this will be absolutely correct from JMM perspective.

apangin
  • 92,924
  • 10
  • 193
  • 247
  • Thank you so much for such constructive answer, Andrey! But actually, does it means that Tiered Compilation may provoke reordering? Or such thing happens only by CPU level? – SlandShow Mar 29 '20 at 11:43
  • 2
    @SlandShow JIT may emit machine instructions not in the program order (that's what we see here). At the same time, CPU may execute machine instructions out-of-order. This happens independently on a lower level. – apangin Mar 29 '20 at 11:58
  • 2
    *"But actually, does it means that Tiered Compilation may provoke reordering?"* - Any native code compilation can provoke reordering. This answer demonstrates that the Tiered Compilation feature is not relevant to your problem with reordering. – Stephen C Mar 29 '20 at 15:25
  • 1
    There seems to be a misleading focus on "reordering" as an actual thing, when discussing multi-threading. The real fun starts when a JIT compiles the entire loop to the equivalent of `x += N; y += N;` That's an easy-to-understand performance improvement which may *look like* performing all N updates of x followed by all N updates of y. Perceiving updates out-of-order doesn't always imply that updates happened that way. – Holger Mar 30 '20 at 10:31