对外部类的“this"引用如何通过发布内部类实例转义? [英] How does `this` reference to an outer class escape through publishing inner class instance?

查看:17
本文介绍了对外部类的“this"引用如何通过发布内部类实例转义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

之前的问题略有不同但要求是/否的答案但我正在寻找这本书中缺少的解释(Java并发实践),这个明显的大错误将如何被恶意或意外利用.

<块引用>

一个对象或其内部状态可以通过的最终机制发布是发布一个内部类实例,如图代码清单 3.7 中的 thisEscape.当 ThisEscape 发布EventListener,它隐式发布封闭的 ThisEscape实例也是如此,因为内部类实例包含一个隐藏的对封闭实例的引用.

清单 3.7.隐式允许 this 引用转义.别这样做.

public class ThisEscape {public ThisEscape(事件源源){source.registerListener(新事件监听器(){public void onEvent(Event e) {做某事(e);}});}}

<块引用>

3.2.1.安全施工规范

ThisEscape 说明了逃逸的一个重要特例——当this 引用在构造过程中逃逸.当内EventListener 实例已发布,封闭的 ThisEscape 实例也已发布实例.但是一个对象只处于可预测的、一致的状态在它的构造函数返回之后,所以从它的内部发布一个对象构造函数可以发布一个不完整构造的对象.这是即使发布是构造函数中的最后一条语句,也为真.如果 this 引用在构造期间转义,则对象为认为构造不正确.[8]

[8] 更具体地说,this 引用不应该从线程直到构造函数返回之后.这个参考可以是由构造函数存储在某处,只要它不被使用另一个线程,直到构建完成.清单 3.8 中的 SafeListener使用这种技术.

不要让 this 引用在构造过程中逃逸.

在构造完成之前,有人如何针对此编写代码以到达 OuterClass?第一段斜体提到的隐藏的内部类引用是什么?

解决方案

请参阅这篇文章.清楚地解释了当你让 this 逃脱时会发生什么.

这里是跟进,并附有进一步说明.

这是 Heinz Kabutz 精彩的时事通讯,其中讨论了这个话题和其他非常有趣的话题.我强烈推荐它.

这是从链接中获取的示例,它显示了this 引用如何转义:

public class ThisEscape {私有最终整数;public ThisEscape(事件源源){source.registerListener(新事件监听器(){public void onEvent(Event e) {做某事(e);}});数量 = 42;}私有无效 doSomething(事件 e){如果(数量!= 42){System.out.println("在" + 处检测到竞争条件新日期());}}}

<块引用>

当它被编译时,javac 生成两个类.外部类看起来像这样:

public class ThisEscape {私有最终整数;public ThisEscape(事件源源){source.registerListener(new ThisEscape$1(this));数量 = 42;}私有无效 doSomething(事件 e){如果(数量!= 42)System.out.println("在" + new Date()) 处检测到竞争条件;}静态无效访问$ 000(ThisEscape _this,事件事件){_this.doSomething(事件);}}

<块引用>

接下来是匿名内部类:

class ThisEscape$1 实现了 EventListener {final ThisEscape this$0;ThisEscape$1(ThisEscape thisescape) {this$0 = thisescape;极好的();}public void onEvent(Event e) {ThisEscape.access$000(this$0, e);}}

在这里,在外部类的构造函数中创建的匿名内部类被转换为包访问类,该类接收对外部类的引用(允许 this 转义的那个).为了使内部类能够访问外部类的属性和方法,在外部类中创建了一个静态包访问方法.这是access$000.

这两篇文章展示了实际的转义是如何发生的,以及可能会发生什么.

'what' 基本上是一种竞争条件,当尝试使用尚未完全初始化的对象时,它可能导致 NullPointerException 或任何其他异常.在这个例子中,如果一个线程足够快,它可能会运行 doSomething() 方法而 num 尚未正确初始化为 42.在第一个链接中,有一个测试正好说明了这一点.

缺少关于如何针对此问题/功能进行编码的几行.我只能考虑坚持一套(可能不完整的)规则/原则来避免这个问题和其他类似问题:

  • 仅在构造函数中调用 private 方法
  • 如果您喜欢肾上腺素并想从构造函数中调用 protected 方法,请执行此操作,但将这些方法声明为 final,以便它们不能被子类覆盖
  • 从不在构造函数中创建内部类,无论是匿名的、本地的、静态的还是非静态的
  • 在构造函数中,不要将this直接作为参数传递给任何东西
  • 避免上述规则的任何传递组合,即不要在从构造函数内部调用的privateprotected final 方法中创建匿名内部类
  • 使用构造函数只构造类的一个实例,让它只初始化类的属性,使用默认值或提供的参数

如果您需要做更多事情,请使用构建器或工厂模式.

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?

解决方案

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.

这篇关于对外部类的“this"引用如何通过发布内部类实例转义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆