class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
I share the same confusion as the author of this question while reading the JLS chapter 17 on JMM. I can literally understand in the described situation f.y could be 0. But this is just like reciting a rule without any serious thinking on it.
It would be really hard to remember the rule if I haven't seen a mistake caused by not following the rule.I have searched through the net but cannot find anyone who gave an example for a situation where f.y could be 0, nor can I come up with mine one example.
I think it may only happens in some rare situation but I am just eager to see one.Hope anyone could give a code demo where f.y could be proved to have been at least once the value of 0.
The following example shows when y
could be 0
.
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
FinalFieldExample() {
x = 3;
f = this;
for (int i = 0; i < 1000000; i++) {
// simulate processing
}
y = 4;
}
static void writer() {
new FinalFieldExample();
}
static void reader() {
System.out.println(f.x); // guaranteed to see 3
System.out.println(f.y); // could see 0
}
public static void main(String[] args) {
new Thread(FinalFieldExample::writer).start();
new Thread(FinalFieldExample::reader).start();
}
}
Please note that this is not caused by reodering of initializations by the JIT compiler. The problem occurs because this
was published, or rather became visible to other threads, during construction. Here is a post about the same JLS documentation and a similar example.
To create an example that shows this problem caused by reordering you need to understand how the reordering works but this is not a deterministic thing. Here someone has posted a very nice answer that explains the reordering and linked many resources.
The details of what the JIT can or will do is nondeterministic. Looking at millions of samples of runs will not produce a meaningful pattern because reorderings are subjective, they depend on very specific details such as CPU arch, timings, heuristics, graph size, JVM vendor, bytecode size, etc... We only know that the JIT will assume that the code runs in a single threaded environment when it does not need to conform to the JMM. In the end, the JIT matters very little to your multithreaded code.
It is very hard to construct an example where your code "breaks" because of reordering. The JIT compiler will obviously check whatever you are doing in your code and won't do reoderings where they would "break" the logic. You are right that this may happen in some rare situations.
Here and here some people have constructed examples that show the reordering. You can see that this is a very low-level-ish effect. The documentation only says that it can happen but doesn't explain when. You shouldn't take this as a "rule" that you need to be aware of. It's just some additional information for you.