16

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?

Community
  • 1
  • 1
Tom
  • 433
  • 3
  • 9
  • according to the book, the reason is "an object is in a predictable, consistent state only after the constructor _returns_" (emphasis mine). object construction is only guaranteed to be complete when the constructor returns, therefore, your happens-before relationship is too soon (it has to be _after_ the constructor returns). – jtahlborn Feb 14 '16 at 21:52
  • I agree with your emphasis here -- the built-in guarantees about e.g. 'final' semantics require you to first return from the constructor. But my question is what specific properties are abandoned by not waiting until that point? One reading is that the automagic "final variables are guaranteed to be safely initialized" property is no longer guaranteed for you, but that you can manually still assert ordering either through happens-before or explicit memory barriers (if such things exist in Java). Another reading is, well, nasal demons. I'm trying to find a more precise explanation – Tom Feb 14 '16 at 22:00
  • 1
    right, the happens-before would seem to guarantee that normal member assignment would be "done". however, to me, object creation involves a number of other "internal" processes (memory allocation, jvm management handling, internal mutex, etc). you aren't guaranteed that an object is completely valid until the constructor returns. honestly, i'm really not sure what you are looking for here. the spec says "you aren't guaranteed" and that allows the jvm implementor to do whatever they want until the constructor completes. just because it "might work" on a given jvm doesn't mean anything. – jtahlborn Feb 14 '16 at 22:57

1 Answers1

5

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.

Community
  • 1
  • 1
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • Thank you for both answers! So if I understand correctly: I can rely on normal happens-before orderings as I believed. However, this *does* mean that if I unsafely publish a `Foo`, I could see improper initialization. e.g. if that Executor uses the instance to populate a `final Foo` member variable in some wrapper, then users of that wrapper could see a `null` (though users who *do* see non-null would see properly initialized Strings). Is that correct? – Tom Feb 15 '16 at 12:04
  • If `Foo` leaks itself (`foo`) in constructor to `ex`, and `ex` sees `foo` through normal happens-before, and `ex` creates `w=new W(foo)`, in which `W.foo` is `final`, and then `ex` publishes `w` unsafely (say through a non-volatile static variable), and another thread reads `w` non-null, ---- that thread will see `w.foo`, `w.foo.str1`, `w.foo.str2` all properly initialized. – ZhongYu Feb 15 '16 at 16:26