《java并发编程实战》- 关于this引用溢出

书中3.2中关于this引用溢出例子:
隐式地使this引用逸出(不要这么做):

public class ThisEscape {
    public ThisEscape(EventSource source){
        source.registerListener(
                new EventListener(){
                    public void onEvent(Event e){
                        doSomething()e;
                    }
                });
    }
}

这段代码的问题是对象逸出

这将导致this逸出,所谓 逸出,就是在不该发布的时候发布了一个引用。

当我们声明一个非静态内部类时,编译器会添加对外部类的一个隐式引用。 即非静态的内部类会持有外部类的一个隐式引用

在构造过程中使this引用逸出的一个常见错误是:在构造过程中启动一个线程。单对象在其构造函数中创建一个线程时,无论是显式创建(通过将它传给构造函数)还是隐式创建(由于Thread或Runnable是该对象的一个内部类),this引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看见它。

当我们实例化ThisEscape对象时,会调用source的registerListener方法,这时便启动了一个线程,而且这个线程持有了通过注册的内部类持有了外部ThisEscape对象引用,但此时ThisEscape对象却没有实例化完成(还没有返回一个引用),所以我们说,此时造成了一个this引用逸出,即还没有完成的实例化ThisEscape对象的动作,却已经暴露了对象的引用。其他线程访问还没有构造好的对象,可能会造成意料不到的问题。

以下是网友分享的解答,让我真正理解this逸出的理由(了解底层实现原理和学习看源码的重要性):

1 **内部类、匿名内部类都可以访问外部类的对象的域,为什么会这样,实际上是因为内部类构造的时候,会把外部类的对象this隐式的作为一个参数传递给内部类的构造方法,这个工作是编译器做的,**他会给你内部类所有的构造方法添加这个参数,所以你例子里的匿名内部类在你构造ThisEscape时就把ThisEscape创建的对象隐式的传给匿名内部类了。

2 最近在看《Java 并发编程实战》,个人的理解:
首先,看里面的 doSomething(e) 方法,这个方法应该是在 ThisEscape 中,不然就无法解释。也就是说,通过 doSomething(e) 方法可以修改 ThisEscape 中的属性或者调用 ThisEscape 中的其他方法。
例子中的代码,在多线程环境下,会出现这样一种情况:
线程 A 和线程 B 同时访问 ThisEscape 构造方法,这时线程 A 访问构造方法还为完成(可以理解为 ThisEscape 为初始化完全),此时由于 this 逸出,导致 this 在 A 和 B 中都具有可见性,线程 B 就可以通过 this 访问 doSomething(e) 方法,导致修改 ThisEscape 的属性。也就是在 ThisEscape 还为初始化完成,就被其他线程读取,导致出现一些奇怪的现象。
这也就是 this 逸出。

通过 《Java 并发编程实战》 官网的书本 example 源码包,也证实了 doSomething 的确是 ThisEscape 中的方法。

package net.jcip.examples;

/**
 * ThisEscape
 * <p/>
 * Implicitly allowing the this reference to escape
 *
 * @author Brian Goetz and Tim Peierls
 */
public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        });
    }

    void doSomething(Event e) {
    }


    interface EventSource {
        void registerListener(EventListener e);
    }

    interface EventListener {
        void onEvent(Event e);
    }

    interface Event {
    }
}

书中给了解决方法,使用工厂方法防止this引用在构造过程中逸出。

public class SafeListener {
    private final EventListener listener;

    private SafeListener(){
        listener = new EventListener(){
            public void onEvent(Event e){
                doSomething(e);
            }
        }
    }

    public static SafeListener newInstance(EventSource source){
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

在这个构造中,我们看到的最大的一个区别就是:当构造好了SafeListener对象(通过构造器构造)之后,我们才启动了监听线程,也就确保了SafeListener对象是构造完成之后再使用的SafeListener对象。

不在构造方法中注册监听器,就不会创建持有外部引用的线程,this引用也就不会逸出。

对于这样的技术,书里面也有这样的注释:

具体来说,只有当构造函数返回时,this引用才应该从线程中逸出。构造函数可以将this引用保存到某个地方,只要其他线程不会在构造函数完成之前使用它。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值