3

Does Java allows output 1, 0? I've tested it very intensively and I cannot get that output. I get only 1, 1 or 0, 0 or 0, 1.

public class Main {
    private int x;
    private volatile int g;

    // Executed by thread #1
    public void actor1(){
       x = 1;
       g = 1;
    }

    // Executed by thread #2
    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

Why?

On my eye it is possible to get 1, 0. My reasoning. g is volatile so it causes that memory order will be ensured. So, it looks like:

actor1:

(1) store(x, 1)
(2) store(g, 1)
(3) memory_barrier // on x86

and, I see the following situation: reorder store(g, 1) before store(x,1) (memory_barrier is after (2)). Now, run thread #2. So, g = 1, x = 0. Now, we have expected output. What is incorrect in my reasoning?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Gilgamesz
  • 4,727
  • 3
  • 28
  • 63
  • 1
    `println` is a synchronized method - so whatever you're testing is going to benefit from that additional synchronization. You could probably remove the volatile keyword and get the same result (depending on your CPU etc.)... – assylias Jul 16 '17 at 22:31
  • How could it ever output `1, 0`? The fields are initialized to `0`, then at some time later assigned `1`. – Bohemian Jul 16 '17 at 22:34
  • I've edited to avoid a confusion. – Gilgamesz Jul 16 '17 at 22:34
  • And no, 1,0 is not a legal output (regardless of the unwanted synchronization). – assylias Jul 16 '17 at 22:34
  • 1
    @assylias, why? – Gilgamesz Jul 16 '17 at 22:35
  • 3
    `volatile` is not specified in terms of memory barriers. If you want a deep understanding of `volatile` and Java thread synchronization in general, read the [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4). – user2357112 Jul 16 '17 at 22:44
  • @Gilgamesz "reorder `store(g, 1)` before `store(x,1)`" is not a valid optimisation (it would not comply with the volatile semantics). – assylias Jul 16 '17 at 22:47
  • @markspace I agree with "Memory visibility is not guaranteed for x until you write g" (hence you could see x=0 in actor2 even if x=1 has been executed). But once g is read in actor2, the JMM guarantees that you will see x=1. – assylias Jul 17 '17 at 06:39
  • @Gilgamesz what may be confusing you is that the barrier ***is*** the volatile store/load, and not a separate operation. – assylias Jul 17 '17 at 06:46
  • @markspace [it isn't legal](https://stackoverflow.com/a/65078088/1059372) – Eugene Nov 30 '20 at 17:18
  • OK re-reading this, for the precise case given, it's not legal to see `1,0` However it's really only for this exact sort of case and also making some assumptions about what is being asked here. It could be in other very similar situations that it's legal, even expected, to see something like `1,0`. – markspace Nov 30 '20 at 17:39

4 Answers4

2

Any actions before a volatile write happen before (HB) any subsequent volatile read of the same variable. In your case, the write to x happens before the write to g (due to program order).

So there are only three possibilities:

  • actor2 runs first and x and g are 0 - output is 0,0
  • actor1 runs first and x and g are 1 because of the happens before relationship HB - output is 1,1
  • the methods run concurrently and only x=1 is executed (not g=1) and the output could be either 0,1 or 0,0 (no volatile write so no guarantee)
assylias
  • 321,522
  • 82
  • 660
  • 783
  • 1
    you said "Any actions before a volatile write happen before any subsequent volatile read of the same variable." I don't understand you. After all, `x` is not the same variable as `g`. – Gilgamesz Jul 16 '17 at 23:03
  • `x=1` is an action that (due to program order) happens before `g=1` which is a volatile write. `put_on_screen_without_sync(g);` is a volatile read. So any read of `x` after that statement will benefit from the volatile synchronization and will return 1. – assylias Jul 16 '17 at 23:08
  • 1
    This is a fundamental guarantee of the Java memory model. See chapter 17 of the jls for more details. – assylias Jul 16 '17 at 23:09
  • @Gilgamesz It means exactly that: any non-volatile writes that happened before the volatile write will be visible after a volatile read. – biziclop Jul 16 '17 at 23:29
  • @assylias, could you look at https://stackoverflow.com/questions/45151763/analyzing-of-x86-output-generated-by-jit-in-the-context-of-volatile? – Gilgamesz Jul 17 '17 at 21:31
  • it seems to me that you deduce that happens-before is an effect of program order (which is correct) and later (again, correctly) invoke that rule with `volatile`, as far as I can see you are correct, it's just that this misses the proper JLS explanation, imho. I [added it](https://stackoverflow.com/a/65078088/1059372) as an addendum. – Eugene Nov 30 '20 at 17:17
2

No, this isn't possible. According to the JMM, anything that was visible to thread 1 when it writes to a volatile field becomes visible to thread 2 when it reads that field.

There is another example similar to yours provided here:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}
ck1
  • 5,243
  • 1
  • 21
  • 25
1

You will never see 1, 0, but properly explaining this is not going to be easy, spec wise. First let's get some obvious things out of the door. The specification says:

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

This means that on the writing thread side, hb(x, g) and on the reading side hb(g, x). But this is only so, if you would have to reason about each thread individually, as the chapter about Program order says::

Among all the inter-thread actions performed by each thread t...

So if you imagine running each thread at a time, then happens-before would be correct for each of them, individually. But you don't. Your actors (I am sure you use jcstress there) run concurrently. So relying on "program order" for reasoning is not enough (neither it is correct).

You need to somehow synchronize these two actions now - the reading and the writing. And here is how the specification says it can be done:

A write to a volatile variable synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

And later says:

If an action x synchronizes-with a following action y, then we also have hb(x, y).

If you put all of these together now:

          (hb)              (hb)             (hb)
write(x) ------> write(g) -------> read(g) -------> read(x)

This is also called to "transitively" close program order and synchronizes-with order. Since there is hb on every step, seeing 1, 0 (a racy read), is impossible according to the spec.

Eugene
  • 117,005
  • 15
  • 201
  • 306
0

No, and in fact this property of volatile is used in classes like ConcurrentHashMap to implement a lock-free happy path, roughly like this:

volatile int locked = 0;
...
void mutate() {
    if (Unsafe.compareAndSwapInt(locked,0,1)) { 
    /*this isn't exactly how you call this method, but the point stands: 
      if we read 0, we atomically replace it with 1 and continue on the happy 
      path */
       //we are happy
       //so we mutate the structure and then
       locked = 0;           
    } else {
       //contended lock, we aren't happy
    }
}

Since writes before a volatile write can't be reordered after the volatile write, and reads after volatile read can't be reordered before the volatile read, code like this indeed works as a "lockless locking".

biziclop
  • 48,926
  • 12
  • 77
  • 104