Search code examples
javavolatilespecifications

Can't understand example of volatile in Java specification


I got general understanding what volatile means in Java. But reading Java SE Specification 8.3.1.4 I have a problem understanding the text beneath that certain volatile example.

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.

How is

j never greater than i

, but at the same time

any given invocation of method two might observe a value for j that is much greater than the value observed for i

??

Looks like contradiction.

I got j greater than i after running sample program. Why use volatile then? It gives almost the same result without volatile (also i can be greater than j, one of previous examples in specs). Why is this example here as an alternative to synchronized?


Solution

  • I think the point of the example is to emphasize that you need to take care and ensure the order when using volatile; the behavior may be counter-intuitive and the example demonstrates it.

    I agree that the wording there is a bit obscure and it is possible to provide more explicit and clear example for multiple cases, but there is no contradiction.

    The shared value is the value at the same moment. If two threads read values of i and of j at exactly the same moment, the value of j will never be observed greater than i. volatile guarantees keeping order of reads and updates as in the code.

    However, in the sample, print + i and + j are two different operations separated by an arbitrary amount of time; hence, j can be observed larger than i, because it can be updated arbitrary number of times after the read of i and before the read of j.

    The point of using volatile is that when you concurrently update and access volatile variables with the right order, you can make assumptions that are not possible in principle without volatile.

    In the sample above, the order of access in two() does not allow to conclude with a confidence which variable is greater or equal.

    Consider, however, if the sample was changed to System.out.println("j=" + j + " i=" + i);

    Here you can assert with a confidence that the printed value of j is never larger than the printed value of i. This assumption will not hold without volatile for two reasons.

    First, updates i++ and j++ can be executed by compiler and hardware in an arbitrary order and in reality may execute as j++;i++. If from other thread you then access j and i after j++ but before i++, you can observe, say, j=1 and i=0, regardless of the access order. volatile guarantees that this will not happen and it will execute operations in the order that is written in your source.

    Second, volatile guarantees that another thread will see most recent values changed by another thread, as long as it accesses it in the later point of time after the last update. Without volatile, there can be no assumptions about the observed value. In theory, the value can stay for another thread zero forever. The program may print two zeros, zero and an arbitrary number, etc. from past updates; the observed value in other threads may be less than the current value that the updater thread sees after an update. volatile guarantees that you will see the value in a second thread after the update in the first.

    While the second guarantee may seem as a consequence of the first (the order guarantee), they are in fact orthogonal.

    Regarding synchronized, it allows to execute a sequence of non-atomic operations, like i++;j++ as an atomic operation, e.g. if one thread does synchronized i++;j++ and another does synchronized System.out.println("i=" + i + " j=" + j);, the first thread may not perform increment sequence while the second prints and the result will be correct.

    But this comes at a cost. First, synhronized has a performance penalty by itself. Second, more important, not always such behavior is required and the blocked thread wastes time, reducing the system throughput (e.g. you can do so many i++;j++; during System.out).