Section 3.2.1 of Goetz's "Java Concurrency in Practice" contains the following rule:
Do not allow the
this
reference to escape during construction
I understand that, in general, allowing this
to escape can lead to other threads seeing incompletely constructed versions of your object and violate the initialization safety guarantee of final
fields (as discussed e.g. here)
But is it ever possible to safely leak this
? In particular, if you establish a happen-before
relationship prior to the leakage?
For example, the official Executor Javadoc says
Actions in a thread prior to submitting a
Runnable
object to anExecutor
happen-before its execution begins, perhaps in another thread
My naive reading understanding of the Java memory model this is that something like the following should be safe, even though it's leaking this
prior to the end of the constructor:
public final class Foo {
private final String str1;
private String str2;
public Foo(Executor ex) {
str1 = "I'm final";
str2 = "I'm not";
ex.execute(new Runnable() {
// Oops: Leakage!
public void run() { System.out.println(str1 + str2);}
});
}
}
That is, even though we have leaked this
to a potentially malicious Executor
, the assignments to str1
and str2
happen-before the leakage, so the object is (for all intents and purposes) completely constructed, even though it has not been "completely initialized" per JLS 17.5.
Note that I also am requiring that the class be final
, as any subclass's fields would be initialized after the leakage.
Am I missing something here? Is this actually guaranteed to be well-behaved? It looks to me like an legitimate example of "Piggybacking on synchronization" (16.1.4) In general, I would greatly appreciate any pointers to additional resources where these issues are covered.
EDIT: I am aware that, as @jtahlborn noted, I can avoid the issue by using a public static factory. I'm looking for an answer of the question directly to solidify my understanding of the Java memory model.
EDIT #2: This answer alludes to what I'm trying to get at. That is, following the rule from the JLS cited therein is sufficient for guaranteeing visibility of all final
fields. But is it necessary, or can we make use of other happen-before mechanisms to ensure our own visibility guarantees?
You are correct. In general, Java memory model does not treat constructors in any special way. Publishing an object reference before or after a constructor exit makes very little difference.
The only exception is, of course, regarding final
fields. The exit of a constructor where a final field is written to defines a "freeze" action on the field; if this
is published after the freeze
, even without happens-before edges, other threads will read the field properly initialized; but not if this
is published before the freeze
.
Interestingly, if there is constructor chaining, freeze
is defined on the smallest scope; e.g.
-- class Bar
final int x;
Bar(int x, int ignore)
{
this.x = x; // assign to final
} // [f] freeze action on this.x
public Bar(int x)
{
this(x, 0);
// [f] is reached!
leak(this);
}
Here leak(this)
is safe w.r.t. this.x
.
See my other answer for more details on final
fields.
If final
seems too complicated, it is. My advice is -- forget it! Do not ever rely on final
field semantics to publish unsafely. If you program is properly synchronized, you don't need to worry about final
fields or their delicate semantics. Unfortunately, the current climate is to push final
fields as much as possible, creating an undue pressure on programmers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With