1.对象发布的几种形式
对象发布是指使对象能够被其作用域之外的线程访问。常见的对象发布形式包括以下几种。
-
将对象引用存储到public变量中。例如:
public Map<String, Integer> registry = new HashMap<String, Integer>();
从面向对象编程的角度来看,这种发布形式不太提倡,因为它违反了信息封
装的原则,不利于问题定位。 -
在非private方法(包括 public、protected、package方法中返回一个对象。例如:
private Map<String, Integer> registry= new HashMap<String, Integer>(); public Map<String, Integer> getRegistry () { return this.registry; }
-
创建内部类,使得当前对象 (this) 能够被这个内部类使用。例如:
public void startTask (final Object task) { Thread t = new Thread(new Runnable() { @Override public void run () { //省略其他代码 } }); t.start() ; }
上述代码中的 “new Runnable()” 所创建的匿名类可用访问其外层类的当前实例this(通过“外层类名.this" 这种语法访问)。也就是说该匿名类的外层类发布了自身的当前实例。
-
通过方法调用将对象传递给外部方法。外部方法(Alien Method)指相对于某个类而言其他类的方法或者该类的可覆盖方法(即非private方法或者非final方法)。将一个对象传递给外部方法也会被视为对象发布。
2.static关键字
static关键字在多线程环境下有其特殊的涵义,它能够保证一个线程即使在未使用其他同步机制的情况下也总是可以读取到一个类的静态变量的初始值(而不是默认值)。但是,这种可见性保障仅限于线程初次读取该变量。如果这个静态变量在相应类初始化完毕之后被其他线程更新过,那么一个线程要读取该变量的相对新值仍然需要借助锁、volatile关键字等同步机制。static关键字仅仅保障读线程能够读取到相应字段的初始值,而不是相对新值。
3.final关键字
由于重排序的作用,一个线程读取到一个对象引用时,该对象可能尚未初始化完毕,即这些线程可能读取到该对象字段的默认值而不是初始值(通过构造器或者初始化语句指定的值)。在多线程环境下 final 关键字有其特殊的作用:当一个对象被发布到其他线程的时候,该对象的所有 final 字段(实例变量 )都是初始化完毕的,即其他线程读取这些字段的时候所读取到的值都是相应字段的初始值(而不是默认值)。而非 final 字段没有这种保障,即这些线程读取该对象的非 final 字段时所读取到的值可能仍然是相应字段的默认值 。对于引用型 final 字段,final 关键字还进一步确保该字段所引用的对象已经初始化完毕,即这些线程读取该字段所引用的对象的各个字段时所读取到的值都是相应字段的初始值 。当一个对象的引用对其他线程可见的时候,这些线程所看到的该对象的 final 字段必然是初始化完毕的。 final 关键字的作用仅是这种有序性的保障,它并不能保障包含final 字段的对象的引用自身对其他线程的可见性。
4.安全发布与逸出
安全发布就是指对象以一种线程安全的方式被发布。当一个对象的发布出现我们不期望的结果或者对象发布本身不是我们所期望的时候,我们就称该对象逸出 (Escape)。一个对象在其初始化过程中没有出现 this 逸出,我们就称该对象为正确创建的对象。要安全发布一个正确创建的对象,我们可以根据情况从以下几种方式中选择。
- 使用 static 关键字修饰引用该对象的变量。
- 使用 final 关键字修饰引用该对象的变量。
- 使用 volatile 关键字修饰引用该对象的变量。
- 使用 AtomicReference 来引用该对象。
- 对访问该对象的代码进行加锁 。