什么是对象发布:
使一个对象能够被当前范围之外的代码所使用。
示例:
@Slf4j
public class UnsafePublish {private String[] states = {"a", "b", "c"};public String[] getStates() {return states;}public static void main(String[] args) {UnsafePublish unsafePublish = new UnsafePublish();log.info("{}", Arrays.toString(unsafePublish.getStates()));unsafePublish.getStates()[0] = "d";log.info("{}", Arrays.toString(unsafePublish.getStates()));}
}
通过public访问级别发布了类的域(states),在类的任何外部线程都可以访问这些域。这样的发布对象是不安全的,因为不知道其他线程是否会修改这个域。简单的说通过unsafePublish发布了这个类的实例。
什么是对象逸出:
一个错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见。
示例:
@Slf4j
public class Escape {private int thisCanBeEscape = 0;public Escape () {new InnerClass();}private class InnerClass {public InnerClass() {log.info("{}", Escape.this.thisCanBeEscape);}}public static void main(String[] args) {new Escape();}
}
Escape类的构造方法还没有构造完成,它的内部类InnerClass就能得到该类的对象引用,称之为对象逸出。
再看另外一种逃逸:
public class Escape {private int id = 0;private String name = null;public Escape() {new Thread(new MyRun()).start();new Thread(new MyRun()).start();name = "zhangsan";}private class MyRun implements Runnable {@Overridepublic void run() {System.out.println(Escape.this.name);System.out.println(Escape.this.id);}}public static void main(String[] args) {new Escape();}
在Escape构造方法中,原本是要初始化该类的属性name的值为zhangsan,但是因为在构造方法中,启动线程,可能没有执行到name="zhangsan",线程就已经执行了,导致线程中name的值为null值。
解决方法:
在构造函数执行完之前,要避免使用Object.this这种引用和避免在构造函数中启动线程。
安全发布的四种方式:
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保持到volitale类型域中或者AtomicReference对象中
- 将对象的引用保存到某个正确的构造对象的final类型域中
- 将对象的引用保持到一个由锁保护的域中