Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
315 views
in Technique[技术] by (71.8m points)

java - How does `this` reference to an outer class escape through publishing inner class instance?

This was asked slightly differently earlier but asking for a yes/no answer but I'm looking for the explanation that's missing from the book (Java Concurrency in Practice), of how this apparent big mistake would be exploited maliciously or accidentally.

A final mechanism by which an object or its internal state can be published is to publish an inner class instance, as shown in ThisEscape in Listing 3.7. When ThisEscape publishes the EventListener, it implicitly publishes the enclosing ThisEscape instance as well, because inner class instances contain a hidden reference to the enclosing instance.

Listing 3.7. Implicitly Allowing the this Reference to Escape. Don't do this.

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

3.2.1. Safe Construction Practices

ThisEscape illustrates an important special case of escape—when the this references escapes during construction. When the inner EventListener instance is published, so is the enclosing ThisEscape instance. But an object is in a predictable, consistent state only after its constructor returns, so publishing an object from within its constructor can publish an incompletely constructed object. This is true even if the publication is the last statement in the constructor. If the this reference escapes during construction, the object is considered not properly constructed.[8]

[8] More specifically, the this reference should not escape from the thread until after the constructor returns. The this reference can be stored somewhere by the constructor so long as it is not used by another thread until after construction. SafeListener in Listing 3.8 uses this technique.

Do not allow the this reference to escape during construction.

How would someone code against this to get to the OuterClass before it's finished constructing? What is the hidden inner class reference mentioned in italics in the first paragraph?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Please see this article. There it's clearly explained what could happen when you let this escape.

And here is a follow-up with further explanations.

It's Heinz Kabutz amazing newsletter, where this and other very interesting topics are discussed. I highly recommend it.

Here is the sample taken from the links, which show how the this reference escapes:

public class ThisEscape {
  private final int num;

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

  private void doSomething(Event e) {
    if (num != 42) {
      System.out.println("Race condition detected at " +
          new Date());
    }
  }
}

When it gets compiled, javac generates two classes. The outer class looks like this:

public class ThisEscape {
  private final int num;

  public ThisEscape(EventSource source) {
    source.registerListener(new ThisEscape$1(this));
    num = 42;
  }

  private void doSomething(Event e) {
    if (num != 42)
      System.out.println(
          "Race condition detected at " + new Date());
  }

  static void access$000(ThisEscape _this, Event event) {
    _this.doSomething(event);
  }
}

Next we have the anonymous inner class:

class ThisEscape$1 implements EventListener {
  final ThisEscape this$0;

  ThisEscape$1(ThisEscape thisescape) {
    this$0 = thisescape;
    super();
  }

  public void onEvent(Event e) {
    ThisEscape.access$000(this$0, e);
  }
}

Here the anonymous inner class created in the constructor of the outer class is converted to a package-access class that receives a reference to the outer class (the one that is allowing this to escape). For the inner class to have access to the attributes and methods of the outer class, a static package-access method is created in the outer class. This is access$000.

Those two articles show both how the actual escaping occurs and what might happen.

The 'what' is basically a race condition that could lead to a NullPointerException or any other exception when attempting to use the object while not yet fully initialized. In the example, if a thread is quick enough, it could happen that it runs the doSomething() method while num has not yet been correctly initialized to 42. In the first link there's a test that shows exactly that.

EDIT: A few lines regarding how to code against this issue/feature were missing. I can only think about sticking to a (maybe incomplete) set of rules/principles to avoid this problem and others alike:

  • Only call private methods from within the constructor
  • If you like adrenaline and want to call protected methods from within the constructor, do it, but declare these methods as final, so that they cannot be overriden by subclasses
  • Never create inner classes in the constructor, either anonymous, local, static or non-static
  • In the constructor, don't pass this directly as an argument to anything
  • Avoid any transitive combination of the rules above, i.e. don't create an anonymous inner class in a private or protected final method that is invoked from within the constructor
  • Use the constructor to just construct an instance of the class, and let it only initialize attributes of the class, either with default values or with provided arguments

If you need to do further things, use either the builder or the factory pattern.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...