3.2. Publication and Escape(公开和逃逸)
Publishing an object means making it available to code outside of its current scope, such as by storing a reference to it where other code can find it, returning it from a non private method, or passing it to a method in another class. In many situations, we want to ensure that objects and their internals are not published. In other situations, we do want to publish an object for general use, but doing so in a thread-safe manner may require synchronization. Publishing internal state variables can compromise encapsulation and make it more difficult to preserve invariants; publishing objects before they are fully constructed can compromise thread safety. An object that is published when it should not have been is said to have escaped. Section 3.5 covers idioms for safe publication; right now, we look at how an object can escape.
发布一个对象意味着使得该对象在当前的范围以外可见,例如存储一个其他代码可以利用的对该对象的应用,从一个非private方法中返回该对象,或者将其传递给其余类的方法。在很多情况下,我们可能会期望确保对象及其内部状态不要公开。在别的情形下,我们可能会希望对某些应用公开状态,但是这需要使用同步机制,以线程安全的方式进行。公开内部状态变量会破坏封装,这会使得维持状态一致变得极其困难。在一个对象没有被良好构建的情况下公开对象有可能会破坏线程安全性。公开一个本来不应该公开的对象就是所谓的逃逸。3.5节中涉及到了一些与安全公开有关的数据,现在首先让我们看一下在什么情况下对象可能会逃逸。
The most blatant form of publication is to store a reference in a public static field, where any class and thread could see it, as in Listing 3.5. The initialize method instantiates a new HashSet and publishes it by storing a reference to it into knownSecrets.
发布的一个最差的形式就是在一个public static域中保存对象的引用,在这样的域中,任何对象和线程都可能会看到该对象,见Listing3.5. initialize初始化了一个HashSet,然后将通过在knownSecrets中保存一个引用的方式来发布该对象。
Listing 3.5. Publishing an Object.
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
Publishing one object may indirectly publish others. If you add a Secret to the published knownSecrets set, you've also published that Secret, because any code can iterate the Set and obtain a reference to the new Secret. Similarly, returning a reference from a nonprivate method also publishes the returned object. UnsafeStates in Listing 3.6 publishes the supposedly private array of state abbreviations.
公开一个对象可能会间接地公开其他对象。如果你在knownSecrets集合中增加一个Secret对象,Secret对象同样会被公开,因为任何代码都可以遍历这个集合,然后获得对Secret对象的应用。同样,从一个非private方法中获取应用同样会公开被返回的对象。Listing3.6中的UnsafeStates公开了看起来是私有的缩写状态数组。
Listing 3.6. Allowing Internal Mutable State to Escape. Don't Do this.
class UnsafeStates {
private String[] states = new String[] {
"AK", "AL" ...
};
public String[] getStates() { return states; }
}
Publishing states in this way is problematic because any caller can modify its contents. In this case, the states array has escaped its intended scope, because what was supposed to be private state has been effectively made public.
以这种方式公开状态会存在很多问题,因为任何调用都可能会修改其内容。在这种情况下,数组的状态逃逸出内部范围,因为本来应该是私有的状态被置成了公开状态。
Publishing an object also publishes any objects referred to by its non private fields. More generally, any object that is reachable from a published object by following some chain of non private field references and method calls has also been published.
公开某个对象也会公开其非private域所引用的所有对象。更为普遍的是,任何能够从public对象通过非private域引用和方法调用链访问的对象都是公开的。
From the perspective of a class C, an alien method is one whose behavior is not fully specified by C. This includes methods in other classes as well as override able methods (neither private nor final) in C itself. Passing an object to an alien method must also be considered publishing that object. Since you can't know what code will actually be invoked, you don't know that the alien method won't publish the object or retain a reference to it that might later be used from another thread.
从某一个类C的角度看,一个外部方法的行为并不能够被类C完全控制。这包括在其他类中包含的方法,以及在C内部可以被重载的方法。将一个对象传递给外部方法必须要考虑该对象的公开性。由于你无法确定什么样的代码会被调用,你也无法确定外部方法是否会公开该对象。
Whether another thread actually does something with a published reference doesn't really matter, because the risk of misuse is still present.[7] Once an object escapes, you have to assume that another class or thread may, maliciously or carelessly, misuse it. This is a compelling reason to use encapsulation: it makes it practical to analyze programs for correctness and harder to violate design constraints accidentally.
另外的线程是否会真的利用公开引用做某件事情实际上并不是十分关键,因为这种误用的危险无论如何都将存在。一旦某个对象逃逸,你必须假设别的类或者线程会恶意的、粗心的使用该类。这就是强制使用封装的原因,封装使得程序的安全性分析变得可行,而且可以避免违反设计约束的偶然性发生。
[7] If someone steals your password and posts it on the alt.free-passwords newsgroup, that information has escaped: whether or not someone has (yet) used those credentials to create mischief, your account has still been compromised. Publishing a reference poses the same sort of risk.
如果有人盗用了你的密码,并将该密码发布到了免费密码联盟的新闻组中的话,你的信息就已经逃逸了。无论是否有人使用该许可做过坏事情,你的账号都已经被破坏了。发布一个对象的应用会导致同样的威胁。
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.
一个对象及其内部状态可能会由其内部类的实例被公开,如同Listing3.7中的ThisEscape类那样。ThisEscape类公开了EventListener对象,这就隐式的公开了外部类ThisEscape的实例,因为内部类实例中保存一个隐形的对其外部类的应用。
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.
ThisEscape类展现了一种非常重要的逃逸场景,这就是在对象被构造的时候发生逃逸。
当内部EventListener实例被公开以后,该实例中也包含其外部类ThisEscape类的实例。但是一个对象只有在其构造器返回之后,该对象才能够达到一种可以预测的、状态可持续的状态。所以从一个对象的构造器中可能会对一个没有被完全构建的对象进行了公开。即使公开的动作是在构造器的最后一行,这种做法也是不对的。如果在对象被构造的过程中被公开的话,对象就会被认为没有合适的被构建。
更为确切的说法是,在构造器返回之前,“this”引用不应该被构造器置于到其他地方,除非这个应用不会被其他任何线程使用。
Do not allow the this reference to escape during construction.
A common mistake that can let the this reference escape during construction is to start a thread from a constructor. When an object creates a thread from its constructor, it almost always shares its this reference with the new thread, either explicitly (by passing it to the constructor) or implicitly (because the Thread or Runnable is an inner class of the owning object). The new thread might then be able to see the owning object before it is fully constructed. There's nothing wrong with creating a thread in a constructor, but it is best not to start the thread immediately. Instead, expose a start or initialize method that starts the owned thread. (See Chapter 7 for more on service lifecycle issues.) Calling an overrideable instance method (one that is neither private nor final) from the constructor can also allow the this reference to escape.
使得“this”应用在构造的时候逃逸的一个常见错误时从构造器中打开一个线程。如果一个对象从构造器中构建一个线程,这个对象将会与该线程一起共享其应用,无论是以隐式的方式还是显式的方式。新的线程可以看到在该对象被完整的创建之前的状态。在构造器内部创建线程没有任何问题,但是最好不用立刻启动该线程。可以在start方法或者initialize方法中启动拥有的线程。从构造器中调用一个可以被重写的实例方法也可能会使得应用逃逸。
If you are tempted to register an event listener or start a thread from a constructor, you can avoid the improper construction by using a private constructor and a public factory method, as shown in SafeListener in Listing 3.8.
如果你试图从构造器中注册一个事件监听器或者开启一个线程的话,你可以通过使用私有的构造器和一个公开的工厂方法来实现。
Listing 3.8. Using a Factory Method to Prevent the this Reference from Escaping During Construction.
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
Publishing an object means making it available to code outside of its current scope, such as by storing a reference to it where other code can find it, returning it from a non private method, or passing it to a method in another class. In many situations, we want to ensure that objects and their internals are not published. In other situations, we do want to publish an object for general use, but doing so in a thread-safe manner may require synchronization. Publishing internal state variables can compromise encapsulation and make it more difficult to preserve invariants; publishing objects before they are fully constructed can compromise thread safety. An object that is published when it should not have been is said to have escaped. Section 3.5 covers idioms for safe publication; right now, we look at how an object can escape.
发布一个对象意味着使得该对象在当前的范围以外可见,例如存储一个其他代码可以利用的对该对象的应用,从一个非private方法中返回该对象,或者将其传递给其余类的方法。在很多情况下,我们可能会期望确保对象及其内部状态不要公开。在别的情形下,我们可能会希望对某些应用公开状态,但是这需要使用同步机制,以线程安全的方式进行。公开内部状态变量会破坏封装,这会使得维持状态一致变得极其困难。在一个对象没有被良好构建的情况下公开对象有可能会破坏线程安全性。公开一个本来不应该公开的对象就是所谓的逃逸。3.5节中涉及到了一些与安全公开有关的数据,现在首先让我们看一下在什么情况下对象可能会逃逸。
The most blatant form of publication is to store a reference in a public static field, where any class and thread could see it, as in Listing 3.5. The initialize method instantiates a new HashSet and publishes it by storing a reference to it into knownSecrets.
发布的一个最差的形式就是在一个public static域中保存对象的引用,在这样的域中,任何对象和线程都可能会看到该对象,见Listing3.5. initialize初始化了一个HashSet,然后将通过在knownSecrets中保存一个引用的方式来发布该对象。
Listing 3.5. Publishing an Object.
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
Publishing one object may indirectly publish others. If you add a Secret to the published knownSecrets set, you've also published that Secret, because any code can iterate the Set and obtain a reference to the new Secret. Similarly, returning a reference from a nonprivate method also publishes the returned object. UnsafeStates in Listing 3.6 publishes the supposedly private array of state abbreviations.
公开一个对象可能会间接地公开其他对象。如果你在knownSecrets集合中增加一个Secret对象,Secret对象同样会被公开,因为任何代码都可以遍历这个集合,然后获得对Secret对象的应用。同样,从一个非private方法中获取应用同样会公开被返回的对象。Listing3.6中的UnsafeStates公开了看起来是私有的缩写状态数组。
Listing 3.6. Allowing Internal Mutable State to Escape. Don't Do this.
class UnsafeStates {
private String[] states = new String[] {
"AK", "AL" ...
};
public String[] getStates() { return states; }
}
Publishing states in this way is problematic because any caller can modify its contents. In this case, the states array has escaped its intended scope, because what was supposed to be private state has been effectively made public.
以这种方式公开状态会存在很多问题,因为任何调用都可能会修改其内容。在这种情况下,数组的状态逃逸出内部范围,因为本来应该是私有的状态被置成了公开状态。
Publishing an object also publishes any objects referred to by its non private fields. More generally, any object that is reachable from a published object by following some chain of non private field references and method calls has also been published.
公开某个对象也会公开其非private域所引用的所有对象。更为普遍的是,任何能够从public对象通过非private域引用和方法调用链访问的对象都是公开的。
From the perspective of a class C, an alien method is one whose behavior is not fully specified by C. This includes methods in other classes as well as override able methods (neither private nor final) in C itself. Passing an object to an alien method must also be considered publishing that object. Since you can't know what code will actually be invoked, you don't know that the alien method won't publish the object or retain a reference to it that might later be used from another thread.
从某一个类C的角度看,一个外部方法的行为并不能够被类C完全控制。这包括在其他类中包含的方法,以及在C内部可以被重载的方法。将一个对象传递给外部方法必须要考虑该对象的公开性。由于你无法确定什么样的代码会被调用,你也无法确定外部方法是否会公开该对象。
Whether another thread actually does something with a published reference doesn't really matter, because the risk of misuse is still present.[7] Once an object escapes, you have to assume that another class or thread may, maliciously or carelessly, misuse it. This is a compelling reason to use encapsulation: it makes it practical to analyze programs for correctness and harder to violate design constraints accidentally.
另外的线程是否会真的利用公开引用做某件事情实际上并不是十分关键,因为这种误用的危险无论如何都将存在。一旦某个对象逃逸,你必须假设别的类或者线程会恶意的、粗心的使用该类。这就是强制使用封装的原因,封装使得程序的安全性分析变得可行,而且可以避免违反设计约束的偶然性发生。
[7] If someone steals your password and posts it on the alt.free-passwords newsgroup, that information has escaped: whether or not someone has (yet) used those credentials to create mischief, your account has still been compromised. Publishing a reference poses the same sort of risk.
如果有人盗用了你的密码,并将该密码发布到了免费密码联盟的新闻组中的话,你的信息就已经逃逸了。无论是否有人使用该许可做过坏事情,你的账号都已经被破坏了。发布一个对象的应用会导致同样的威胁。
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.
一个对象及其内部状态可能会由其内部类的实例被公开,如同Listing3.7中的ThisEscape类那样。ThisEscape类公开了EventListener对象,这就隐式的公开了外部类ThisEscape的实例,因为内部类实例中保存一个隐形的对其外部类的应用。
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.
ThisEscape类展现了一种非常重要的逃逸场景,这就是在对象被构造的时候发生逃逸。
当内部EventListener实例被公开以后,该实例中也包含其外部类ThisEscape类的实例。但是一个对象只有在其构造器返回之后,该对象才能够达到一种可以预测的、状态可持续的状态。所以从一个对象的构造器中可能会对一个没有被完全构建的对象进行了公开。即使公开的动作是在构造器的最后一行,这种做法也是不对的。如果在对象被构造的过程中被公开的话,对象就会被认为没有合适的被构建。
更为确切的说法是,在构造器返回之前,“this”引用不应该被构造器置于到其他地方,除非这个应用不会被其他任何线程使用。
Do not allow the this reference to escape during construction.
A common mistake that can let the this reference escape during construction is to start a thread from a constructor. When an object creates a thread from its constructor, it almost always shares its this reference with the new thread, either explicitly (by passing it to the constructor) or implicitly (because the Thread or Runnable is an inner class of the owning object). The new thread might then be able to see the owning object before it is fully constructed. There's nothing wrong with creating a thread in a constructor, but it is best not to start the thread immediately. Instead, expose a start or initialize method that starts the owned thread. (See Chapter 7 for more on service lifecycle issues.) Calling an overrideable instance method (one that is neither private nor final) from the constructor can also allow the this reference to escape.
使得“this”应用在构造的时候逃逸的一个常见错误时从构造器中打开一个线程。如果一个对象从构造器中构建一个线程,这个对象将会与该线程一起共享其应用,无论是以隐式的方式还是显式的方式。新的线程可以看到在该对象被完整的创建之前的状态。在构造器内部创建线程没有任何问题,但是最好不用立刻启动该线程。可以在start方法或者initialize方法中启动拥有的线程。从构造器中调用一个可以被重写的实例方法也可能会使得应用逃逸。
If you are tempted to register an event listener or start a thread from a constructor, you can avoid the improper construction by using a private constructor and a public factory method, as shown in SafeListener in Listing 3.8.
如果你试图从构造器中注册一个事件监听器或者开启一个线程的话,你可以通过使用私有的构造器和一个公开的工厂方法来实现。
Listing 3.8. Using a Factory Method to Prevent the this Reference from Escaping During Construction.
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}