什么是对象发布:
使一个对象能够被当前范围之外的代码所使用。
示例:
@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 {
@Override
public 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类型域中
- 将对象的引用保持到一个由锁保护的域中