views:

135

answers:

3

Read that the following code is an example of "unsafe construction" as it allows this reference to escape. I couldn't quite get how 'this' escapes. I am pretty new to the java world. Can any one help me understand this.

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}
+1  A: 

My guess is that doSomething method is declared in ThisEscape class, in which case reference certainly can 'escape'.
I.e., some event can trigger this EventListener right after its creation and before execution of ThisEscape constructor is completed. And listener, in turn, will call instance method of ThisEscape.

I'll modify your example a little. Now variable var can be accessed in doSomething method before it's assigned in constructor.

public class ThisEscape {
    private final int var;

    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            }
        );

        // more initialization
        // ...

        var = 10;
    }

    // result can be 0 or 10
    int doSomething(Event e) {
        return var;
    }
}
Nikita Rybak
That is not the happiest example since the book where this problem comes from (Concurrency in Practice) says that even if the registerListener is the last line in the constructor, the poorly constructed object can still escape
Pablo Fernandez
@Pablo How? (I'm not challenging the book, just curious about 'failing' example)
Nikita Rybak
Me too, I think it has something to do with the fact that the outer object is visible in the `onEvent` method. And java does not guarantee proper variable initialization unless fields are declared final (This also from that book). Note: I haven't finish reading it :P
Pablo Fernandez
Hopefully Jon Skeet will jump in and unveil the mystery soon :) (and there goes our upvotes too)
Pablo Fernandez
@Pablo Well, make sure to post here if you find explanation in the book :) It's not a Bible after all, we're not expected to just believe every statement written.
Nikita Rybak
@Nikita - see my answer for the real explanation.
Stephen C
+6  A: 

I had the exact same doubt.

The thing is that every class that gets instantiated inside other class has a reference to the enclosing class in the variable $this.

This is what java calls a synthetic, it's not something you define to be there but something java does for you automatically.

If you want to see this for yourself put a breakpoint in the doSomething(e) line and check what properties EventListener has.

Pablo Fernandez
@pablo thanks a lot!
iJeeves
@Pablo - see my answer for the real explanation.
Stephen C
I think this is an important part of the problem; it explains precisely *how* `this` escapes, which is what the question asks (*why* escaping is bad is not an explicit part of the question).
erickson
+6  A: 

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:

  1. 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.

  2. 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.

Stephen C
Could you please be a bit more clear? what is a "happens-before" relation. Or maybe provide an example where the exposure actually happens?
Pablo Fernandez
And thx for the downvotes by the way
Pablo Fernandez
So is this essentially a "bug" in the JVM memory model?
Gabe
@Pablo - fix your answer and I'll remove the downvote :-)
Stephen C
So how much of http://www.ibm.com/developerworks/java/library/j-jtp0618.html is out of date?
Gabe
@Gabe - I wouldn't have said "bug". More like it is a "feature". The memory model is designed to allow correctly written multi-threaded Java applications to run fast on multiprocessor machines. If the memory model didn't have features like this, then every Java read or write of an object attribute would have to go all the way to main memory. This would result in an enormous performance hit.
Stephen C
@Gabe - without reading it in detail, I've no reason to believe that any of it is out of date. That article doesn't say that there is a "bug" in the memory model ... if that's what you are implying.
Stephen C
Stephen: Goetz says "the JMM is being revised under Java Community Process JSR 133, which will (among other things) change the semantics of volatile and final to bring them more in line with general intuition." That was written in 2002. How much has changed in the past 8 years?
Gabe
@Gabe - The memory model changed from JLS 2 to JLS 3 as a result of JSR 133. To see the differences, compare chapter 17 of the JLS 2nd and 3rd editions. AFAIK, none of the changes invalidate Goetz's 2002 article. His book was revised in 2006, and describes the JLS 3rd edition memory model. The memory model hasn't changed since JLS 3rd edition / Java 5.0.
Stephen C
wow! thanks, probably the best answer I ever got on SO.
iJeeves
Yes, that looks right.
erickson