Search code examples
javajava-memory-modeljls

Can r1 == 0 and r2 == 0 occur without volatile in this Java program?


Do I understand correctly that if I run run1() and run2() in parallel, the result r1 == 0 and r2 == 0 is impossible under any circumstances, even though a and b are not volatile?

public class Example {

    private int a = 0;
    private int b = 0;

    public void run1() {
        b = 1; // (1)
        int r2 = a; // (2)
    }

    public void run2() {
        a = 2; // (3)
        int r1 = b; // (4)
    }
}

My conclusion is based on this statement from the Java Memory Model, p. 17.4.5: "If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)."

  • (1) and (2) are actions of the same thread and (1) comes before (2) in program order. Therefore, (1) happens-before (2).
  • Similarly, (3) happens-before (4).

This program contains only four actions, resulting in 4! possible executions. By analysing each possible execution, I conclude that any execution leading to r1 == 0 and r2 == 0 would violate the happens-before relationships described above and is therefore illegal.


Solution

  • Your analysis is wrong because it misses these two points from 17.4.5. Happens-before Order:

    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.

    That first point means that an implementation can execute the code fragment

        b = 1; // (1)
        int r2 = a; // (2)
    

    in reverse order

        int r2 = a; // (2)
        b = 1; // (1)
    

    Since action (2) does not depend on a result of action (1) this produces a result consistent with a legal execution and is therefore allowed (what any calling method on the same thread observes is independent of the execution order of those two statements since it can only observe any changes after both statements have finished).

    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.

    Two unsynchronized threads executing run1() and run2() in parallel do not share a happens-before relationship. You can therefore not expect that one thread sees the actions of the other thread in any specific order.