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 followin
Sleeping 3 seconds before assigning the field in the constructor does not matter because for value != value
to be true
, the first read of value
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
package com.mypackage;
public class Test {
public static void main(String[] args) {
new Worker().start();
int i = 1;
while (true) {
new Holder(i++);
}
}
}
class Holder {
private int value;
Holder(int value) {
Worker.holder = this;
this.value = value;
}
void assertSanity() {
if (getValue() != getValue()) throw new AssertionError();
}
private int getValue() { return value; }
}
class Worker extends Thread {
static Holder holder = new Holder(0);
@Override
public void run() {
while (true) {
holder.assertSanity();
}
}
}
By disallowing Holder#getValue()
to be inlined, we prevent the two subsequent reads of value
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:
int snapshot1 = getValue();
// <--- window of vulnerability, where the observed value can change
// if you chose to sleep 3 seconds, you would want to do it here
// takes very little time, less than 1 nanosecond
int snapshot2 = getValue();
if (snapshot1 != snapshot2) throw new AssertionError();
So the first read of value
could produce the default value of int
which is 0
(called the stale value and assigned in the Object()
constructor), and the second read could produce the value assigned in the Holder(int)
constructor.
This would happen if for example the value assigned in the constructor were propagated to the thread calling assertSanity()
in the exact moment between the two loads of value
(window of vulnerability).
The same would happen if we delayed the second read in some other way, like:
int snapshot1 = this.value;
Thread.interrupted();
int snapshot2 = this.value;
if (snapshot1 != snapshot2) throw new AssertionError();