【问题标题】:Allowing the this reference to escape允许 this 引用转义
【发布时间】:2013-12-26 19:01:05
【问题描述】:

如果能帮助我理解“Java 并发实践”中的以下内容,我将不胜感激:

调用一个可覆盖的实例方法(一个既不是 来自构造函数的 private 或 final) 也可以允许 这个引用转义。

  1. 这里的“escape”是否仅仅意味着我们可能在实例完全构造之前调用了一个实例方法?
    我没有看到“this”以任何其他方式逃避实例的范围。
  2. “final”如何防止这种情况发生?在创建实例时是否有“final”的某些方面我遗漏了?

【问题讨论】:

  • 好问题。
  • 我刚刚阅读了那段并用谷歌搜索并找到了您的问题

标签: java concurrency


【解决方案1】:
  1. 表示在类外调用代码,并传递this
    该代码将假定实例已完全初始化,否则可能会中断。
    同样,您的类可能会假设某些方法仅在实例完全初始化后才会被调用,但外部代码可能会破坏这些假设。

  2. final 方法不能被覆盖,因此您可以相信它们不会传递 this
    如果您在非final 类的构造函数中调用任何非final 方法,派生类可能会覆盖该方法并在任何地方传递this

    即使您调用final 方法,您仍然需要确保它们是安全编写的——它们不会在任何地方传递this,并且它们本身不会调用任何非final 方法。

【讨论】:

  • 代码“可能”假定类已完全初始化,但方法的约定可以明确禁止此类推断。在某些情况下,基类构造函数可能需要有关派生类的信息,而获取该信息的最实用方法可能是调用一个虚方法,其记录的目的是在基类构造期间被调用。在这种情况下,基类构造函数接受某种对象并将其传递给虚方法可能会有所帮助;这将使派生类构造函数成为可能...
  • ...将有关其自身参数的信息封装到一个对象中,然后使用该信息计算基类构造函数所需的数据。请注意,子派生类的设计可能有些棘手,但有一些模式可以让此类事情得到干净和一致的处理。还要注意,这样的设计将依赖派生类来遵守它们的契约,但这种依赖并不是这种情况所独有的。如果派生类不遵守它们的约定,那么很少有未密封的类可以表现得稳健。
【解决方案2】:

“Escape”表示对部分构造的this 对象的引用可能会传递给系统中的某个其他对象。考虑这种情况:

public Foo {
    public Foo() {
        setup();
    }

    protected void setup() {
       // do stuff
    }
}

public Bar extends Foo implements SomeListener {
    @Override protected void setup() {
        otherObject.addListener(this);
    }
}

问题是新的Bar 对象在其构造完成之前正在向otherObject 注册。现在如果otherObject 开始调用barObject 上的方法,则字段可能尚未初始化,或者barObject 可能处于不一致的状态。对barObjectthis 自身)的引用在准备好之前已“逃逸”到系统的其余部分。

相反,如果setup() 方法在Foo 上是final,则Bar 类不能在其中放置代码,这将使对象在Foo 构造函数完成之前可见。

【讨论】:

    【解决方案3】:

    我相信这个例子是这样的

    public class Foo {
        public Foo() {
            doSomething();
        }
    
        public void doSomething() {
            System.out.println("do something acceptable");
        }
    }
    
    public class Bar extends Foo {
        public void doSomething() {
            System.out.println("yolo");
            Zoom zoom = new Zoom(this); // at this point 'this' might not be fully initialized
        }
    }
    

    因为总是首先调用超级构造函数(隐式或显式),所以总是会为子类调用doSomething。因为上面的方法既不是final也不是private,你可以在一个子类中重写它并做你想做的任何事情,这可能与Foo#doSomething()的本意相冲突。

    【讨论】:

      【解决方案4】:

      secure coding

      示例错误代码:

      final class Publisher {
        public static volatile Publisher published;
        int num;
      
        Publisher(int number) {
          published = this;
          // Initialization
          this.num = number;
          // ...
        }
      }   
      

      如果一个对象的初始化(以及因此它的构造)依赖于构造函数中的安全检查,则当不受信任的调用者获得部分初始化的实例时,可以绕过安全检查。参见规则OBJ11-J。小心让构造函数抛出异常以获取更多信息。

      final class Publisher {
        public static Publisher published;
        int num;
      
        Publisher(int number) {
          // Initialization
          this.num = number;
          // ...
          published = this;
        }
      }
      

      由于该字段是非易失性和非最终的,因此其中的语句 构造函数可以由编译器以这样的方式重新排序 this 引用在初始化语句之前发布 已执行。

      正确代码

      final class Publisher {
        static volatile Publisher published;
        int num;
      
        Publisher(int number) {
          // Initialization
          this.num = number;
          // ...
          published = this;
        }
      }
      

      据说 this 引用在可用时已转义 超出了目前的范围。以下是this的常用方法 引用可以转义:

      Returning this from a non-private, overridable method that is invoked from the constructor of a class whose object is being
      

      构造。 (有关更多信息,请参阅规则 MET05-J。确保 构造函数不调用可覆盖的方法。) 从可变类的非私有方法返回 this,它允许调用者间接操纵对象的状态。这 通常发生在方法链实现中;见规则 VNA04-J。 确保对链式方法的调用是原子的以获取更多信息。 将 this 作为参数传递给从正在构造对象的类的构造函数调用的外星方法。 使用内部类。除非声明了内部类,否则内部类会隐式持有对其外部类实例的引用 静止的。 通过将 this 分配给正在构造对象的类的构造函数中的公共静态变量来发布。 从构造函数中抛出异常。这样做可能会导致代码容易受到终结器攻击;参见规则 OBJ11-J。警惕 让构造函数抛出异常以获取更多信息。 将内部对象状态传递给外星方法。这使得该方法能够检索内部成员对象的 this 引用。

      此规则描述了允许这样做的潜在后果 在对象构造期间引用逃逸,包括比赛 条件和不正确的初始化。例如,声明一个字段 final 通常确保所有线程都以完全的方式看到该字段 初始化状态;但是,允许 this 引用转义 在对象构造过程中可以将字段暴露给一个线程中的其他线程 未初始化或部分初始化的状态。规则 TSM03-J。不要 发布部分初始化的对象,它描述了保证 由各种安全发布机制提供,依赖于 符合这个规则。因此,程序不得允许 在对象构造过程中对转义的引用。

      一般来说,重要的是要发现这种情况 引用可能会超出当前上下文的范围。在 特别是,公共变量和方法应该小心 仔细检查。

      【讨论】:

      • 我认为,对于您的Publisher 类,更好的模式是使用某种形式的工厂(可能是static 方法),它创建一个实例并分配published 字段。如果Publisher 不是final,这一点尤其重要。
      • @Jeffrey 这不是我的发布者类,它是直接从安全编码站点获取的。
      猜你喜欢
      • 1970-01-01
      • 2020-05-06
      • 1970-01-01
      • 2012-03-10
      • 2015-08-29
      • 2018-10-04
      • 1970-01-01
      • 2019-03-29
      相关资源
      最近更新 更多