Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to understand volatile example in Java Language Specification?

I think example of volatile in Java specification is a little wrong.

In 8.3.1.4. volatile Fields, it says

class Test {
    static int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

...then method two could occasionally print a value for j that is greater than the value of i, because the example includes no synchronization and, under the rules explained in§17.4, the shared values of i and j might be updated out of order.

I think even if these updates are in order, method two may still see j greater than i, since System.out.println("i=" + i + " j=" + j) is not atomic, and i is read before j.

method two is the same like

read i
read j

So it is possible that

read i
i++
j++
read j

In this case method two see a value for j that is greater than i, however updates are NOT out of order.

So out of order is not the only reason to see j > i

Should it be System.out.println("j=" + j + " i=" + i);?

This time out of order is the only reason to see j > i

like image 615
Tim Avatar asked May 24 '19 03:05

Tim


2 Answers

What Holger is saying in his answer is absolutely correct (read it again and accept it), I just want to add that using jcstress, this is even sort of easy to prove. The test itself is just a minor refactor from the Coherence Sample (which is superbe! IMO):

import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Expect;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;

@JCStressTest
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only j updated")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only i updated")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "both updates lost")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "both updated")
@State
public class SOExample {

    private final Holder h1 = new Holder();
    private final Holder h2 = h1;

    @Actor
    public void writeActor() {
        ++h1.i;
        ++h1.j;

    }

    @Actor
    public void readActor(II_Result result) {
        Holder h1 = this.h1;
        Holder h2 = this.h2;

        h1.trap = 0;
        h2.trap = 0;

        result.r1 = h1.i;
        result.r2 = h2.j;
    }

    static class Holder {

        int i = 0;
        int j = 0;

        int trap;
    }

}

Even if you don't understand the code, the point is that running it will show ACCEPTABLE_INTERESTING as absolutely possible outcomes; be that with volatile int i = 0; volatile int j = 0; or without that volatile.

like image 44
Eugene Avatar answered Sep 18 '22 00:09

Eugene


The examples are more than “a little wrong”.

First, you are right that even without reordering, j may appear greater than i in this example. This is even acknowledged later in the same example:

Another approach would be to declare i and j to be volatile:

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i and j occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. Therefore, the shared value for j is never greater than that for i, because each update to i must be reflected in the shared value for i before the update to j occurs. It is possible, however, that any given invocation of method two might observe a value for j that is much greater than the value observed for i, because method one might be executed many times between the moment when method two fetches the value of i and the moment when method two fetches the value of j.

Of course, it is abstruse to say “the shared value for j is never greater than that for i”, just to say right in the next sentence “It is possible … [to] observe a value for j that is much greater than the value observed for i”.

So j is never greater than i, except when it is observed to be much greater than i? Is it supposed to say that “a little greater” is impossible?

Of course not. This statement makes no sense and seems to be the result of trying to separate some objective truth like “the shared value” from “the observed value” whereas in fact, there is only observable behavior in a program.

This is illustrated by the wrong sentence:

This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i and j occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread.

Even with volatile variables, there is no such guarantee. All that the JVM must guarantee, is that the observed behavior doesn’t contradict the specification, so when you invoke one() thousand times in a loop, for example, an optimizer may still replace it with an atomic increment by thousand, if it can preclude the possibility of another thread witnessing the presence of such an optimization (other than deducing from the higher speed).

Or in other words, how many times a variable (resp. its memory location) is actually accessed, is not observable and hence, not specified. It doesn’t matter anyway. All that matters to an application programmer, is that j can be greater than i, whether the variables are declared volatile or not.

Swapping the order of the reads of i and j within two() might make it a better example, but I think, it would be best, if JLS §8.3.1.2 did not try to explain the meaning of volatile colloquially, but just stated that it imposes special semantics according to the memory model and left it to the JMM to explain it in a formally correct way.

Programmers are not supposed to master concurrency just by reading 8.3.1.4., so the example is pointless here (in the best case; the worst case would be creating the impression that this example was sufficient to understand the matter).

like image 162
Holger Avatar answered Sep 18 '22 00:09

Holger