该篇文章内容参考自从根上理解什么是 this 逃逸,以及如何避免!
一、主要概念
this:表示对当前类的一个引用,可以用于区分当前类和其它外部类中同名的变量、方法或构造方法。
this逃逸:类构造函数在返回实例之前,线程便持有该对象的引用。
this逃逸是出现在多线程并发时,线程A还未初始化完成,而线程B就已经访问了线程A未初始化完成的属性而出现的一系列问题。
二、出现情况
在类的构造函数中初始化this对象以及属性,但在构造函数之外访问了该this引用,则可能由于构造器还未完全完成以及指令重排序的原因就可能会访问到该 this 还未被初始化的属性,导致后续出现一系列的错误。
public class ThisEscape {
// final 常量会保证在构造器内完成初始化(但是仅限于未发生 this 逃逸的情况下)
final int i;
//尽管实例变量有初始值,但是还实例化完成
int j;
static ThisEscape obj;
public ThisEscape() {
i = 1;
j = 1;
// 将this对象逃逸给全局变量obj且在其它线程中使用
obj = this;
}
public static void main(String[] args) {
//线程:读取对象引用,访问 i、j 变量
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//可能会发生初始化失败的情况解释:实例变量 i 的初始化被重排序到构造器外,此时 i 还未被初始化
new ThisEscape();
ThisEscape objB = obj;
try {
System.out.println(objB.j);
} catch (NullPointerException e) {
System.out.println("发生空指针错误:普通变量 j 未被初始化");
}
try {
System.out.println(objB.i);
} catch (NullPointerException e) {
System.out.println("发生空指针错误:final 变量 i 未被初始化");
}
}
});
thread.start();
}
}
上述代码中 ThisEscape 类的实例对象在执行构造方法,将当前对象this指给了全局变量obj,而obj则在其它线程中被使用了,这个时候如果 ThisEscape 类的构造函数还未执行完成时,其它线程已经在访问在构造函数中初始化的属性,此时就会出现this逃逸的现象
另外如果在构造函数中启动内部类包含的线程,且在内部类的线程中引用构造函数对应类的对象ThisEscape.this则也会导致this逃逸
类名.this一般用于内部类调用外部类的对象时使用,因为内部类使用this调用的是内部类的域和方法,为了加以区别,所以使用类名.this
public class ThisEscape {
final int i;
int j;
public ThisEscape() {
i = 1;
j = 1;
new Thread(new RunablTest()).start();
}
//内部类实现 Runnable:引用外部类
private class RunablTest implements Runnable{
@Override
public void run() {
try {
System.out.println(ThisEscape.this.j);
} catch (NullPointerException e) {
System.out.println("发生空指针错误:普通变量 j 未被初始化");
}
try {
System.out.println(ThisEscape.this.i);
} catch (NullPointerException e) {
System.out.println("发生空指针错误:final 变量 i 未被初始化");
}
}
}
public static void main(String[] args) {
new ThisEscape();
}
}
使用时间流程解释即为:
三、避免方法
- 禁止在构造函数中将this对象指向外部对象
- 禁止在构造函数中启动其它多线程
总结:在多线程并发编程时要注意避免以上this逃逸出现的情况,尽量不要在构造方法中将当前this对象逃逸到外部以及不要在构造方法中启动多线程等