1 基本概念
发布(Publish):发布一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。例如:
- 将一个指向该对象的引用保存在其他代码可以访问的地方;
- 在某一个非私有方法中返回该引用;
- 将引用传递到其他类的方法中。
逸出:当某个不应该发布的对象被发布时,这种情况就被称为逸出。
2 什么情况下会发布一个对象?
2.1 发布一个对象最简单的方法是将对象的引用保存在一个公有的静态变量中,以便任何类和线程都能看到该对象。
public static Set<Secret> knownSecrets;public void initialize(){knownSecrets = new HashSet<Secret>();
}
当发布一个对象时,可能会间接的发布其他对象。例如上面的代码,如果向集合knownSecrets中添加一个Secret对象,那么该对象也会发布,因为任何代码都可以遍历这个集合,并获得对这个Secret对象的引用。
2.2 同样,如果从非私有方法中返回一个引用,那么同样会发布返回的对象。
class UnsafeStates{private String[] states = new String[]{"A","B","C" };public String[] getStates(){ return states; }
}
当发布一个对象时,在该对象的非私有域中引用的所有对象都将会被发布。一般来说,如果一个已经发布的对象能够通过非私有的变量引用和方法调用到达其他的对象,那么这些对象也都会被发布。
“外部方法”:指行为上并不完全由本类来规定的方法,包括其他类中定义的方法以及本类中可以被改写的方法(既不是私有方法也不是终止[final]方法)。当把一个对象传递给外部方法时,就相当于发布了该对象。
2.3 发布一个内部类的实例。如下代码,当ThisEscape发布EventLinstener时,也隐含发布了自己本身。因为内部类默认持有外部类的一个引用。
public class ThisEscape{public ThisEscape(EventSource source){source.registerListener(new EventListener(){public void onEvent(Event e){doSomething(e);}});}
}
3 怎么安全地发布一个对象?
在上述代码中,ThisEscape中给出了一个特殊示例,即this引用在构造函数中逸出。
当且仅当对象的构造函数返回时,对象才处于可预测的、一致的状态。因此,当从对象的构造函数中发布对象时,只是发布了一个尚未完成构造的对象。即使发布对象语句放在构造函数的最后一行也是如此。如果this引用在构造函数中逸出,则这种对象被认为是不正确的构造。
安全发布:
- 不可变对象:满足不可变性的所有要求:状态不可修改、所有域都是final、正确的构造过程。
- 事实不可变对象:对象从技术上看是可变的,但其状态在发布后不会再改变。
- 可变对象:对象在构造后可以修改。
- 任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有同步。
- 在没有额外同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。
- 可变对象必须通过安全方式来发布,并且必须是线程安全的或由某个锁保护起来。
安全发布的常用模式:
安全发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式安全地发布:
- 在静态初始化函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域中或AtomicReferance对象中。
- 将对象的引用保存在某个正确构造对象的final类型域中。
- 将对象的引用保存到一个由锁保护的域中。
通常,要发布一个静态构造对象,最简单最安全的方式是使用静态的初始化器:
public static Holder holder = new Holder(42);
静态初始化构造器由JVM在类的初始化阶段执行。由于JVM内部存在同步机制,因此通过这种方式初始化的任何对象可以被安全地发布。