Java-构造函数中this引用逸出的问题

Java-构造函数中this引用逸出的问题

晚风撩人 发布于 2017-07-18 字数 226 浏览 1430 回复 5

在构造函数中,如果注册listener监听器,或者start()新的线程,为什么会存在this指针逸出的风险。为什么在这种情况下,监视器与新线程对于this引用是可见的,这种可见性怎么样会造成线程的不安全。希望得到大侠的指导。再下不胜感激。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

归属感 2017-10-26 5 楼

这涉及到了Java中发布和逸出的问题,具体你可以参见《java并发编程实践 中文版》的第三章第二节的内容,应该对你有很大帮助。

夜无邪 2017-10-05 4 楼

this溢出或者对象溢出,跟对象的实例化有关系。
new一个对象的时候,对象的创建顺序并不是完全按照构造方法中的代码顺序来执行的,即使this的引用是在构造方法的结尾,因为编译器和处理器对指令的重排序会,对象在部分实例化(对象构建未完全)的情况下,this也会有值,这时候,访问该对象得到的就是不稳定的对象,出现的问题就不可预知了。

偏爱自由 2017-10-03 3 楼

Java concurrency in practice 3.2 里面的代码:

public class ThisEscape {
    public ThisEscape(EventSource source){
        source.registerListener(
                new EventListener(){
                    //匿名内部类生成外部类的引用
                    //(因为在内部类自动拥有对外围类所有成员的访问权是通过秘密捕获一个指向那个外围类对象的引用.)
                    //此时如果某个线程在内部类捕获外围类对象的引用的时候,使用了外围类引用,则造成this逃逸?......
                    public void onEvent(Event e){
                        doSomething(e);
                    }
                });
    }
}

另外附上stackoverflow里找到的同样问题的链接:

stackoverflow Java: reference escape

http://stackoverflow.com/questions/3705425/java-reference-escape

我的理解跟最下面投票最少的一样.

(doSomething method is declared in ThisEscape class, in which case reference certainly can 'escape'.I.e., some event can trigger this EventListener right after its creation and before execution of ThisEscape constructor is completed. And listener, in turn, will call instance method of ThisEscape.)

而对于投票最多的回答的第二点不是很懂.
投票最多的答案第二点说的是final属性的问题.(The Java Language Specification 17.5:Final fields also allow programmers to implement thread-safe immutable objects without synchronization.)

不知道有没有高手能帮忙解释下.

虐人心 2017-08-21 2 楼

今天又看JCP,发现书中说33页那个EventListener是ThisEscape的内部类,这样的话,ThisEscape在没有创建完成的情况下,就可能会被EventLister调用(ThisEscape.this.someMethod()),发布的对象,与预想的不一样,所以逸出了。后面的“无论显示的,还是隐式的创建一个线程(由于Thread或者Runnable是该对象的一个内部类)”,然后我也凌乱了,为什么会是一个内部类。不过纵观整节,大概意思就是说不要在对象没有创建完成就发布他。可能这部分都是说内部类吧。33页,“最后一种发布对象或者其内部状态的机制就是发布内部的类实例”,估计都是在发布一个内部类的基础上的。
@see [http://stackoverflow.com/questions/12467101/how-this-escapes-from-a-published-inner-class]

归属感 2017-08-09 1 楼

谈谈自己的理解, 权作 抛砖引玉.

为什么说 "构造函数中this引用逸出的问题"

在构造函数中, this引用逸出, 则此时 可能对象实例还没有完全初始化. 比如此对象实例有final int i=47; 因为i还没有初始化, 外部线程去 查看i, 可能会看到i==null的结果. 这就不对了

说仔细一点, 初始化一个对象, 比如代码new TestClass(), 完成了以下操作

1). 为TestClass对象实例分配内存空间; this指针指向此空间;
2). 调用TestClass的初始化函数; 在TestClass的初始化函数中;
2.1). 首先调用TestClass的父类的初始化函数, 这里是一个递归调用父类初始化函数的过程;
2.2). 执行TestClass自身的初始化逻辑, 如给 对象实例成员 赋初值.

所以说, 在1) 和 2.2) 之间是有 "空窗期"的, 如果这是this被外部线程拿到, 则可以看到 对象实例 未初始化的实例成员. 比如上面说的final int i == null的现象.

上面这些可以参考下面的字节码:

class Sup{}

class Sub extends Sup{
final int i;
Sub(){i=1;}
void test(){new Sub();}
}

字节码为:

class Sup {

Sup();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 return
}

class Sub extends Sup {

final int i;

Sub();
0 aload_0 [this]
1 invokespecial Sup() [10]
4 aload_0 [this]
5 iconst_1
6 putfield Sub.i : int [12]
9 return

void test();
0 new Sub [1]
3 invokespecial Sub() [19]
6 return
}

这里的Sub对象实例 在 Sub.test()的0行就已经 生成了, 也就是说这里this已经指向了 新生成对象实例的内存空间. 但是Sub对象实例的 final成员i, 是在Sub初始化函数的第6行才被赋值.

再说一下 具体到 JCP 的代码, "注册listener监听器,或者start()新的线程,为什么会存在this指针逸出的风险"

因为 内部类 隐式包含了 外部类的 this指针, 这个见下面的示例:

class Outter1{
private class Inner{}
Outter1(){}
}

字节码:

class Outter1 {

Outter1();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 return
}

private class Outter1$Inner {

final synthetic Outter1 this$0;

private Outter1$Inner(Outter1 arg0);
0 aload_0 [this]
1 aload_1 [arg0]
2 putfield Outter1$Inner.this$0 : Outter1 [10]
5 aload_0 [this]
6 invokespecial java.lang.Object() [12]
9 return
}

重点是 内部类里 有这个:
final synthetic Outter1 this$0;
synthetic 表示这个是编译器 自己加上去的. 在内部类的初始化函数中此引用被 赋值.