The example you have posted in your question comes from "Java Concurrency In Practice" by Brian Goetz et al. It is in section 3.2 "Publication and escape". I won't attempt to reproduce the details of that section here. (Go buy a copy for your bookshelf, or borrow a copy from your co-workers!)
The problem illustrated by the example code is that the constructor allows the reference to the object being constructed to "escape" before the constructor finishes creating the object. This is a problem for two reasons:
If the reference escapes, something can use the object before its constructor has completed the initialization and see it in an inconsistent (partly initialized) state. Even if the object escapes after initialization has completed, declaring a subclass can cause this to be violated.
According to JLS 17.5, final attributes of an object can be used safely without synchronization. However, this is only true if the object reference is not published (does not escape) before its constructor finished. If you break this rule, the result is an insidious concurrency bug that might bite you when the code is executed on a multi-core / multi-processor machines.
The ThisEscape
example is sneaky because the reference is escaping via the this
reference passed implicitly to the anonymous EventListener
class constructor. However, the same problems will arise if the reference is explicitly published too soon.
Here's an example to illustrate the problem of incompletely initialized objects:
public class Thing {
public Thing (Leaker leaker) {
leaker.leak(this);
}
}
public class NamedThing extends Thing {
private String name;
public NamedThing (Leaker leaker, String name) {
super(leaker);
}
public String getName() {
return name;
}
}
If the Leaker.leak(...)
method calls getName()
on the leaked object, it will get null
... because at that point in time the object's constructor chain has not completed.
Here's an example to illustrate the unsafe publication problem for final
attributes.
public class Unsafe {
public final int foo = 42;
public Unsafe(Unsafe[] leak) {
leak[0] = this; // Unsafe publication
// Make the "window of vulnerability" large
for (long l = 0; l < /* very large */ ; l++) {
...
}
}
}
public class Main {
public static void main(String[] args) {
final Unsafe[] leak = new Unsafe[1];
new Thread(new Runnable() {
public void run() {
Thread.yield(); // (or sleep for a bit)
new Unsafe(leak);
}
}).start();
while (true) {
if (leak[0] != null) {
if (leak[0].foo == 42) {
System.err.println("OK");
} else {
System.err.println("OUCH!");
}
System.exit(0);
}
}
}
}
Some runs of this application may print "OUCH!" instead of "OK", indicating that the main thread has observed the Unsafe
object in an "impossible" state due to unsafe publication via the leak
array. Whether this happens or not will depend on your JVM and your hardware platform.
Now this example is clearly artificial, but it is not difficult to imagine how this kind of thing can happen in real multi-threaded applications.