基本概念
- 对象的发布是指,使一个对象能够在当前作用域之外的代码中使用(直接访问到对象的引用)
- 对象的逸出是指,一个不应该发布的对象被发布
对象不正确构造
如果this
引用在构造过程中逸出,那么这种对象就被认为是不正确构造。我们应该确保对象的构造过程中不会出现this
引用逸出
构造过程this
引用逸出的例子
public class ThisEscape {
public ThisEscape(EventSource source) {
// 构造方法中,调用了一个外部方法,发布了eventListener对象
// eventListener对象是一个匿名内部类的对象,因此同时也发布了包装类ThisEscape的this对象
source.registerListener(new EventListener() {
public void onEvent(Event e) {
// do something
}
});
}
}
构造过程中使this
引用逸出的一个常见错误是,在构造函数中启动一个线程,this
引用会被新创建的线程共享
对象发布的例子
- 将对象引用保存到一个公有的静态变量中
- 一个对象的公有方法返回该对象的私有域对象,则发布了这个私有域对象
- 当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布
- 当把一个对象传递给某个外部方法(非
private
且非final
方法)时,因其行为无法确定,相当于发布了这个对象 - 发布一个内部类对象,则同时发布了包装类对象
对象的不安全发布
正常情况下,一个对象首先被创建且完整地初始化后,再进行发布。但由于内存重排序的存在,其它线程可能观测到未完整初始化的被发布对象,然后看到对象状态突然发生变化,我们称这种发布为不安全发布
对象不安全发布的例子
// 其它线程观测到引用的更新时,对象可能尚未完整初始化
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
对象的安全发布
对象的安全发布是指,发布对象的引用以及对象的状态必须同时对其它线程可见,也就是保证了其它线程访问到被发布对象的时候,被发布对象一定已经完整地初始化
安全发布的基础是被发布对象是正确构造的
安全发布的常用模式
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到
volatile
类型的域或者AtomicReferance
对象中 - 将对象的引用保存到某个正确构造对象的
final
类型域中 - 将对象的引用保存到一个由锁保护的域中
不可变对象(对象创建以后状态不能修改、所有域都是final
类型、对象是正确构造的)通过任意机制发布都是安全的,不需要使用安全的方式来发布
安全发布的原理
通过happens-before规则中的监视器锁规则实现
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到一个由锁保护的域中
通过happens-before规则中的volatile变量规则实现
- 将对象的引用保存到
volatile
类型的域或者AtomicReferance
对象中
通过final语义实现
- 将对象的引用保存到某个正确构造对象的
final
类型域中 - 不可变对象的任意发布
final语义可以确保,假如对象的final
域在其构造函数中初始化,且该对象是正确构造的,则当该对象的引用对其它线程可见时,该对象的final
域也对其它线程可见。假如对象的final
域是指向一个对象或数组的引用,则此final
域指向的对象或数组中的任意变量(初始化版本)同样对于其它线程可见
参考文档
- Brain Goetz等,Java并发编程实战(Java Concurrency in Practice),机械工业出版社,2012:32-34,41-44
- https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5
PREVIOUSJava容器ConcurrentHashMap源码分析
NEXTHappens-Before关系