对象的发布与逸出简单理解

对象的发布与逸出简单理解

最近来看《并发编程实战》,第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引用的逸出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值