Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Safe to "leak" this-reference in constructor for final class via _happens-before_ relation?

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 an Executor 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?

like image 475
Tom Avatar asked Feb 14 '16 21:02

Tom


1 Answers

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.

like image 164
ZhongYu Avatar answered Nov 16 '22 02:11

ZhongYu