对象的发布与逸出简单理解
最近来看《并发编程实战》,第3.2节有点疑问,记录一下。
定义
发布
:发布(Publish)一个对象的意思是指,使对象在当前作用域之外的代码中使用。逸出
:如果再对象构造完全之前就发布该对象,就会破坏线程安全性。当某个不应该发布的对象被发布时,这种情况就被成为逸出(Escape)
实战
发布比较好理解,像单例模式,还有做初始化操作的时候,都是这样。比如像书中代码片段
public static Set<Secret> knownSecrets;
public void initialize(){
knownSecrets = new HashSet<>()
}
此时的knownSecrets对象的作用域保证任意代码可以遍历这个集合,并且获得这个引用。
但是如果这里是是一个私有对象的时候,就会超过他定义的所在的作用域,即当前这个类。
class UnsafeStates{
private String[] states = new String[]{"AK","AL"};
public String[] getStates(){return states;}
}
如果有以下代码修改了
String[] myStates = getStates();
myStates[0] ="ABC"
//最后结果数组第一个被改为"ABC"
当一个对象传递给某个外部方法时,就相当于发布了这个对象。所以也会保留这个对象的引用,你在修改这个对象的引用的时候,私有的那个变量也会改变。这样就会带来一个风险。
在构造方法没有完成的情况下,把对象发布出去,这就造成
隐式地this引用逸出。同样还是用书中的案例,我这里增加了一个局部变量num,通过查看这个num值可以看出问题。
public class ThisEscape {
//当前类的私有变量
private int num;
//事件源类
private static class EventSource {
public void registerListener(EventListener eventListener){
eventListener.onEvent(new Event());
//sth
}
}
//事件类
private static class Event{
}
//时间监听类
private interface EventListener{
void onEvent(Event event);
}
//------------------------------------------------------------------
//主要是这个构造方法。。。
public ThisEscape(EventSource eventSource){
eventSource.registerListener(new EventListener() {
@Override
public void onEvent(Event event) {
//这里,内部类实现中暴露了当前类的私有变量,而且是在初始化以前
System.out.println("内部方法的num:"+num);
}
});
//继续初始化操作,给num赋值,然后构造方法结束
num=10;
}
public static void main(String[] args) {
ThisEscape threadTest7 = new ThisEscape(new EventSource());
System.out.println("初始化完毕后的num:"+threadTest7.num);
}
}
这个地方最后的结果
内部方法的num:0
初始化完毕后的num:10
这个num在我们匿名方法中在初始化对象钱就拿到了this,也拿到了没有初始化的num的值0。这样就会带来很大的风险。同时,这个也被称为不正确构造。
书中也给我们提供了一个工厂方法来解决这个this在引用构造逸出的问题,我按照上面的例子,增加了一个Private作用范围的常量来接收
public class ThreadTest8 {
//当前类的私有变量
private int num ;
//使用final修饰
private final EventListener listener;
// 准备类
private static class EventSource {
public void registerListener(EventListener eventListener){
eventListener.onEvent(new Event());
System.out.println(eventListener);
//sth
}
}
// 准备类
private static class Event{
}
// 准备类
private interface EventListener{
void onEvent(Event event);
}
//----------------------------------------------
//主要是这个构造方法。。。
//私有化构造方法
//限定这个构造方法的作用域
private ThreadTest8(){
listener = new EventListener() {
@Override
public void onEvent(Event event) {
//内部类实现中暴露了当前类的私有变量,但是作用域限制了当前类
System.out.println("内部方法的num:"+num);
}
};
num = 10;
}
public static ThreadTest8 newInstance(EventSource eventSource){
ThreadTest8 threadTest8 = new ThreadTest8();
eventSource.registerListener(threadTest8.listener);
return threadTest8;
}
public static void main(String[] args) {
ThreadTest8 threadTest8 = newInstance(new EventSource());
System.out.println("初始化完毕后的num:"+threadTest8.num);
}
}
这个时候,最后的结果都会是10了。
内部方法的num:10
初始化完毕后的num:10
因为在构造方法中启动一个线程,无论是显示的还是隐式的,this引用都会被新的线程共享。(内部类的关系)。所以在对象构造前就可以看到他,虽然在创建这个新线程的时候没有错误,所以在这个时候不要立即启动,等待初始化结束后再去调用这个新的线程。最后的工厂方法也是避免了不正确的构造来避免this引用的逸出。