发布(Publish)和逸出(Escape)是Java并发编程中需要注意的问题。
“发布”,简单来说就是提供一个对象的引用给作用域之外的代码。比如return一个对象,或者作为参数传递到其他类的方法中。
“逸出”,是指如果一个类还没有构造结束就已经提供给了外部代码一个对象引用即发布了该对象,此时叫做对象逸出,对象的逸出会破坏线程的安全性。
下面代码是我们经常编写的代码,states变量是一个私有变量,然而getStates()方法将states对象发布,states逃出了其作用域,这样其他类或对象就可以修改该对象:
@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()));
}
}
运行代码输出:
[a, b, c]
[d, b, c]
还有更加隐秘的this逸出,代码如下:
@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的构造函数中相当于创建了一个线程,创建了一个内部类对象,内部类里面使用thisCanBeEscape,有可能在对象还没有发布完成就先使用了,有可能导致this引用在构造过程中逸出,这种对象也因此被认为是不正确构造。
如果要在构造器中创建线程,应该使用专有的start或初始化的方法来统一启动线程,例如可以使用工厂方法和私有构造函数来完成对象的创建和监听器的创建。
安全发布对象4种方法:
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtmoicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中