Java对象的发布

基本概念

  • 对象的发布是指,使一个对象能够在当前作用域之外的代码中使用(直接访问到对象的引用)
  • 对象的逸出是指,一个不应该发布的对象被发布

对象不正确构造

如果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域指向的对象或数组中的任意变量(初始化版本)同样对于其它线程可见

Happens-Before关系#具体实现

参考文档