问题
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
回答1:
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
andj
to bevolatile
: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 methodtwo
to be executed concurrently, but guarantees that accesses to the shared values fori
andj
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 forj
is never greater than that fori
, because each update toi
must be reflected in the shared value fori
before the update toj
occurs. It is possible, however, that any given invocation of methodtwo
might observe a value forj
that is much greater than the value observed fori
, because methodone
might be executed many times between the moment when methodtwo
fetches the value ofi
and the moment when methodtwo
fetches the value ofj
.
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
andj
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).
回答2:
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
.
来源:https://stackoverflow.com/questions/56285669/how-to-understand-volatile-example-in-java-language-specification