在学习并发编程的时候,大多数的资料都会涉及到this逃逸这个问题。
从本质上讲,this代表当前对象名,是一个指向本对象的指针。
首先看两个例子:
第一个:
public class ThisEscape {
private final int var;
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
}
);
// more initialization
// ...
var = 10;
}
// result can be 0 or 10
int doSomething(Event e) {
return var;
}
}
事件监听器一旦注册成功,就能够监听用户的操作,调用对应的回调函数。也就是调用doSomething()这个方法。这个时候是异步的。而对象ThisEscape的构造函数还有可能没有执行完,此时并没有给var变量赋值(var = 0),所以此时doSomething中获取到的数据有可能是0,并不是10.
第二个:
public class ThreadThisEscape{
private int weight = 0;
public ThreadThisEscape(){
weight = 1;
new Thread(new EscapeRunnable()).start();
// 构造函数比较耗时
}
private class EscapeRunnable implements Runnable{
public void run(){
System.out.println(ThreadThisEscape.this.weight);
}
}
public static void main(String[] args){
new ThreadThisEscape();
}
}
这种情况,在构造函数构造的过程中,可能出现指令重排序的问题,所以有可能先执行EscapeRunnable线程的启动,这个时候,访问到的weight也有可能是0.
总结一下:
对于对象的发布和逸出,有2中常见的情况:在构造函数中注册事件监听,在构造函数中启动新线程。代码如下:
第一种:在构造函数中注册事件监听:
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
第二种:在构造函数中启动新线程:
public class ThreadThisEscape {
public ThisEscape() {
new Thread(new EscapeRunnable()).start();
// ...
}
private class EscapeRunnable implements Runnable {
@Override
public void run() {
// ThreadThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸
}
}
}
这2中方式的共同点是:在构造函数中使用内部类,并且代码可能会出现并行运行。也就是说,当内部类代码执行的时候,外部类对象的创建过程还有可能没有结束,这个
时候如果内部类访问外部类的数据,很有可能得到没有正确初始化的数据。
也就是说,this逃逸是说在构造函数返回之前其他线程就持有该对象的引用,调用尚未构造完全的对象的方法可能引发错误。