I'm reading a book "Java concurrency in practice" by Brian Goetz. Paragraphs 3.5 and 3.5.1 contains statements that I can not understand.
Consider the following code:
public class Holder {
private int value;
public Holder(int value) {
this.value = value;
}
public void assertValue() {
if (value != value) throw new AssertionError("Magic");
}
}
class HolderContainer {
// Unsafe publication
public Holder holder;
public void init() {
holder = new Holder(42);
}
}
Author states that:
- In Java, Object constructor first writes default values to all fields before subclass constructor run.
- Therefore it's possible to see field default value as a stale value.
- Thread may see stale value the first time it reads a field and then a more up-to-date value the next time, which is why assertNĀ can throw AssertionError.
So, according to the text, with some unlucky timing it is possible that value = 0; and in the next moment value = 42.
I agree with point 1 that Object constructor firstly fills fields with default values. But I don't understand points 2 & 3.
Let's update authors code and consider the following example:
public class Holder {
int value;
public Holder(int value) {
//Sleep to prevent constructor to finish too early
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}
public void assertValue() {
if(value != value) System.out.println("Magic");
}
}
I've added Thread.sleep(3000), to force thread to wait before object will be fully constructed.
public class Tests {
private HolderContainer hc = new HolderContainer();
class Initialization implements Runnable {
public void run() {
hc.init();
}
}
class Checking implements Runnable {
public void run() {
hc.holder.assertValue();
}
}
public void run() {
new Thread(new Initialization()).start();
new Thread(new Checking()).start();
}
}
In example:
- first thread inits holder object
- second thread calls assertValue
Main Thread runs two threads:
- new Thread(new Initialization()).start(); It tooks 3 seconds to fully construct Holder object
- new Thread(new Checking()).start(); since Holder object still not constructed code will throw an NullPointerException
Therefore, it's impossible to emulate situation when field has default value.
My Questions:
- Author was wrong about this concurrency problem?
- Or It it impossible to emulate behaviour for fields default values?
Sleeping 3 seconds before assigning the field in the constructor does not matter because for
value != value
to betrue
, the first read ofvalue
must produce a different result than the second one, which happens immediately after.The Java Memory Model does not guarantee that values assigned to fields in constructors are visible to other threads after the constructor finishes. To have this guarantee, the field must be
final
.Here's a program that produces the bug on x86. It must be run with the VM option:
-XX:CompileCommand=dontinline,com/mypackage/Holder.getValue
By disallowing
Holder#getValue()
to be inlined, we prevent the two subsequent reads ofvalue
to be collapsed into a single one.This optimization prevents the code in the book from producing the bug. However, the book author is still correct, since this optimization is not mandatory, so from the Java Memory Model perspective, the code is incorrect.
The
assertSanity()
method is equal to:So the first read of
value
could produce the default value ofint
which is0
(called the stale value and assigned in theObject()
constructor), and the second read could produce the value assigned in theHolder(int)
constructor. This would happen if for example the value assigned in the constructor were propagated to the thread callingassertSanity()
in the exact moment between the two loads ofvalue
(window of vulnerability).The same would happen if we delayed the second read in some other way, like: