java this逃逸_this引用逃逸
1、什么是This逃逸?
在构造器构造还未彻底完成前(即实例初始化阶段还未完成),将自身this引用向外抛出并被其他线程复制(访问)了该引用,可能会问到该还未被初始化的变量,甚至可能会造成更大严重的问题。
废话不多说,看一下代码
1 /**
2 * 模拟this逃逸3 *@authorLijian4 *5 */
6 public classThisEscape {7 //final常量会保证在构造器内完成初始化(但是仅限于未发生this逃逸的情况下,具体可以看多线程对final保证可见性的实现)
8 final inti;9 //尽管实例变量有初始值,但是还实例化完成
10 int j = 0;11 staticThisEscape obj;12 publicThisEscape() {13 i=1;14 j=1;15 //将this逃逸抛出给线程B
16 obj = this;17 }18 public static voidmain(String[] args) {19 //线程A:模拟构造器中this逃逸,将未构造完全对象引用抛出
20 /*Thread threadA = new Thread(new Runnable() {21 @Override22 public void run() {23 //obj = new ThisEscape();24 }25 });*/
26 //线程B:读取对象引用,访问i/j变量
27 Thread threadB = new Thread(newRunnable() {28 @Override29 public voidrun() {30 31 //可能会发生初始化失败的情况解释:实例变量i的初始化被重排序到构造器外,此时1还未被初始化
32 ThisEscape objB =obj;33 try{34 System.out.println(objB.j);35 } catch(NullPointerException e) {36 System.out.println("发生空指针错误:普通变量j未被初始化");37 }38 try{39 System.out.println(objB.i);40 } catch(NullPointerException e) {41 System.out.println("发生空指针错误:final变量i未被初始化");42 }43 }44 });45 //threadA.start();
46 threadB.start();47 }48 }
输出结果:这说明ThisEscape还未完成实例化,构造还未彻底结束。
发生空指针错误:普通变量j未被初始化
发生空指针错误:final变量i未被初始化
另一种情况是利用线程A模拟this逃逸,但不一定会发生,线程A模拟构造器正在构造...而线程B尝试访问变量,这是因为
(1)由于JVM的指令重排序存在,实例变量i的初始化被安排到构造器外(final可见性保证是final变量规定在构造器中完成的);
(2)类似于this逃逸,线程A中构造器构造还未完全完成。
所以尝试多次输出(相信我一定会发生的,只是概率相对低),也会发生类似this引用逃逸的情况。
1 /**
2 * 模拟this逃逸3 *@authorLijian4 *5 */
6 public classThisEscape {7 //final常量会保证在构造器内完成初始化(但是仅限于未发送this逃逸的情况下)
8 final inti;9 //尽管实例变量有初始值,但是还实例化完成
10 int j = 0;11 staticThisEscape obj;12 publicThisEscape() {13 i=1;14 j=1;15 //obj = this ;
16 }17 public static voidmain(String[] args) {18 //线程A:模拟构造器中this逃逸,将未构造完全对象引用抛出
19 Thread threadA = new Thread(newRunnable() {20 @Override21 public voidrun() {22 //构造初始化中...线程B可能获取到还未被初始化完成的变量23 //类似于this逃逸,但并不定发生
24 obj = newThisEscape();25 }26 });27 //线程B:读取对象引用,访问i/j变量
28 Thread threadB = new Thread(newRunnable() {29 @Override30 public voidrun() {31 //可能会发生初始化失败的情况解释:实例变量i的初始化被重排序到构造器外,此时1还未被初始化
32 ThisEscape objB =obj;33 try{34 System.out.println(objB.j);35 } catch(NullPointerException e) {36 System.out.println("发生空指针错误:普通变量j未被初始化");37 }38 try{39 System.out.println(objB.i);40 } catch(NullPointerException e) {41 System.out.println("发生空指针错误:final变量i未被初始化");42 }43 }44 });45 threadA.start();46 threadB.start();47 }48 }
2、什么情况下会This逃逸?
(1)在构造器中很明显地抛出this引用提供其他线程使用(如上述的明显将this抛出)。
(2)在构造器中内部类使用外部类情况:内部类访问外部类是没有任何条件的,也不要任何代价,也就造成了当外部类还未初始化完成的时候,内部类就尝试获取为初始化完成的变量
在构造器中启动线程:启动的线程任务是内部类,在内部类中xxx.this访问了外部类实例,就会发生访问到还未初始化完成的变量
在构造器中注册事件,这是因为在构造器中监听事件是有回调函数(可能访问了操作了实例变量),而事件监听一般都是异步的。在还未初始化完成之前就可能发生回调访问了未初始化的变量。
在构造器中启动线程代码实现:
1 /**
2 * 模拟this逃逸2:构造器中启动线程3 *@authorLijian4 *5 */
6 public classThisEscape2 {7 final inti;8 intj;9 publicThisEscape2() {10 i = 1;11 j = 1;12 new Thread(newRunablTest()).start();13 }14 //内部类实现Runnable:引用外部类
15 private class RunablTest implementsRunnable{16 @Override17 public voidrun() {18 try{19 System.out.println(ThisEscape2.this.j);20 } catch(NullPointerException e) {21 System.out.println("发生空指针错误:普通变量j未被初始化");22 }23 try{24 System.out.println(ThisEscape2.this.i);25 } catch(NullPointerException e) {26 System.out.println("发生空指针错误:final变量i未被初始化");27 }28 }29
30 }31 public static voidmain(String[] args) {32 newThisEscape2();33 }34 }
构造器中注册事件,引用网上的一段伪代码将以解释:
public classThisEscape3 {private final intvar;publicThisEscape3(EventSource source) {
//注册事件,会一直监听,当发生事件e时,会执行回调函数doSomething
source.registerListener(//匿名内部类实现newEventListener() {public voidonEvent(Event e) {
//此时ThisEscape3可能还未初始化完成,var可能还未被赋值,自然就发生严重错误
doSomething(e);
}
}
);var= 10;
}// 在回调函数中访问变量
intdoSomething(Event e) {returnvar;
}
}
3、怎样避免This逃逸?
(1)单独编写一个启动线程的方法,不要在构造器中启动线程,尝试在外部启动。
...privateThread t;publicThisEscape2() {
t= new Thread(newEscapeRunnable());
}public voidinitStart() {
t.start();
}
...
(2)将事件监听放置于构造器外,比如new Object()的时候就启动事件监听,但是在构造器内不能使用事件监听,那可以在static{}中加事件监听,这样就跟构造器解耦了
static{
source.registerListener(newEventListener() {public voidonEvent(Event e) {
doSomething(e);
}
}
);
var= 10;
}
}
4、总结
this引用逃逸问题实则是Java多线程编程中需要注意的问题,引起逃逸的原因无非就是在多线程的编程中“滥用”引用(往往涉及构造器中显式或隐式地滥用this引用),在使用到this引用的时候需要特别注意!
同时这会涉及到:final的内存语义,即final域禁止重排序问题(2020.11.22增加),包括写final域与读final域重排序两个规则(参考资料《Java并发编程的艺术》)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
