开发者

Java: reference escape

开发者 https://www.devze.com 2023-01-16 04:50 出处:网络
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

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 t开发者_StackOverflow社区his.

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


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.


The current Java Memory Model was specified in Java 5 (the 3rd edition of the JLS) as a result of JSR 133. Prior to then, memory-related aspects of Java were under-specified. Sources that refer to earlier versions / editions are out of date, but the information on the memory model in Goetz edition 1 is up to date.

There are some technical aspects of the memory model that are apparently in need of revision; see https://openjdk.java.net/jeps/188 and https://www.infoq.com/articles/The-OpenJDK9-Revised-Java-Memory-Model/. However, this work has yet to appear in a JLS revision.


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.


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;
    }
}


I just had the exact same question while reading "Java Concurrency In Practice" by Brian Goetz.

Stephen C's answer (the accepted one) is excellent! I only wanted to add on top of that one more resource I discovered. It is from JavaSpecialists, where Dr. Heinz M. Kabutz analyzes exactly the code example that devnull posted. He explains what classes are generated (outer, inner) after compiling and how this escapes. I found that explanation useful so I felt like sharing :)

issue192 (where he extends the example and provides a race condition.)

issue192b (where he explains what kind of classes are generated after compiling and how this escapes.)


This confused me quite a bit, as well. Looking at the full code example and also just reading it a million times helped me finally see it. Compliments to Stephen C, though, whose answer is quite thorough and offers a simplified example.

The problem is source.registerListener(), which is provided as a member of ThisEscape. Who knows what that method does? We don't. ThisEscape doesn't specify, because it's declared in an interface within ThisEscape.

public class ThisEscape {
    // ...
    
    interface EventSource {
        void registerListener(EventListener e);
    }

    interface EventListener {
        void onEvent(Event e);
    }

    // ...
}

Whatever class implements EventSource provides the implementation for registerListener() and we have no idea what it might do with the provided EventListener. But, because EventListener is a part of ThisEscape, it also contains a hidden reference to it. That's why this example is so tricky. Basically, while ThisEscape is being constructed, a reference to it is published via source.registerListener(<reference>), and who knows what that EventSource will do with it.

It's one of the many great cases for a private constructor and static factory method. You can confine construction to one line in the static factory method, ensuring its completion before passing it to source.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号