咨询热线:15288205895 联系人:黄国强 地址:沙河百沙路新飞达电子科技发展中心
this引用逃逸
来源:同乐城备用 发布时间:2019-06-23 点击量:180
1、什么是This逃逸?
在构造器构造还未彻底完成前(即实例初始化阶段还未完成),将自身this引用向外抛出并被其他线程复制(访问)了该引用,可能会问到该还未被初始化的变量,甚至可能会造成更大严重的问题。
废话不多说,看一下代码
1 /** 2 * 模拟this逃逸 3 * @author Lijian 4 * 5 */ 6 public class ThisEscape { 7 //final常量会保证在构造器内完成初始化(但是仅限于未发生this逃逸的情况下,具体可以看多线程对final保证可见性的实现) 8 final int i; 9 //尽管实例变量有初始值,但是还实例化完成10 int j = 0;11 static ThisEscape obj;12 public ThisEscape() {13 i=1;14 j=1;15 //将this逃逸抛出给线程B16 obj = new ThisEscape();17 }18 public static void main(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(new Runnable() {28 @Override29 public void run() {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 * @author Lijian 4 * 5 */ 6 public class ThisEscape { 7 //final常量会保证在构造器内完成初始化(但是仅限于未发送this逃逸的情况下) 8 final int i; 9 //尽管实例变量有初始值,但是还实例化完成10 int j = 0;11 static ThisEscape obj;12 public ThisEscape() {13 i=1;14 j=1;15 //obj = new ThisEscape();16 }17 public static void main(String[] args) {18 //线程A:模拟构造器中this逃逸,将未构造完全对象引用抛出19 Thread threadA = new Thread(new Runnable() {20 @Override21 public void run() {22 //构造初始化中...线程B可能获取到还未被初始化完成的变量23 //类似于this逃逸,但并不定发生24 obj = new ThisEscape();25 }26 });27 //线程B:读取对象引用,访问i/j变量28 Thread threadB = new Thread(new Runnable() {29 @Override30 public void run() {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 * @author Lijian 4 * 5 */ 6 public class ThisEscape2 { 7 final int i; 8 int j; 9 public ThisEscape2() {10 i = 1;11 j = 1;12 new Thread(new RunablTest()).start();13 }14 //内部类实现Runnable:引用外部类15 private class RunablTest implements Runnable{16 @Override17 public void run() {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 void main(String[] args) {32 new ThisEscape2();33 }34 }
构造器中注册事件,引用网上的一段伪代码将以解释:
public class ThisEscape3 { private final int var; public ThisEscape3(EventSource source) { //注册事件,会一直监听,当发生事件e时,会执行回调函数doSomething source.registerListener( //匿名内部类实现 new EventListener() { public void onEvent(Event e) { //此时ThisEscape3可能还未初始化完成,var可能还未被赋值,自然就发生严重错误 doSomething(e); } } ); var = 10; } // 在回调函数中访问变量 int doSomething(Event e) { return var; }}
3、怎样避免This逃逸?
(1)单独编写一个启动线程的方法,不要在构造器中启动线程,尝试在外部启动。
...private Thread t;public ThisEscape2() { t = new Thread(new EscapeRunnable());}public void initStart() { t.start();}...
(2)将事件监听放置于构造器外,比如new Object()的时候就启动事件监听,但是在构造器内不能使用事件监听,那可以在static{}中加事件监听,这样就跟构造器解耦了
static{ source.registerListener( new EventListener() { public void onEvent(Event e) { doSomething(e); } } ); var = 10; }}
4、总结
this引用逃逸问题实则是Java多线程编程中需要注意的问题,引起逃逸的原因无非就是在多线程的编程中“滥用”引用(往往涉及构造器中显式或隐式地滥用this引用),在使用到this引用的时候需要特别注意!
相关产品
-
调查淫秽色情网站不是一件愉快的事。北京市文化执法总队网络执法队队长沈睿向北京晨报记者介绍,要把这些让人看了脸红心跳的网站进行数据化分析,对它们的服务器设置、经营模式、网站内容、浏览方式、收费途径等事项逐一调查,是一件很繁琐的工作。
-
在这张照片中,是颜色的对比使我们认出来飞行中的金刚鹦鹉,读到这里,请先添加我的个人微信号:bicyclelife,朋友圈每天分享精彩的摄影技巧和时尚大片,我们继续讲,由于鸟身上明亮且有特色的颜色,摄影师才能创作出这幅作品。
-
二是扶贫必扶智。我们愿支持非洲将人力资源潜力转化为人才支撑。中非合作论坛约堡峰会到2018年,中方将为非洲提供3万个政府奖学金和4万个来华培训名额,并为非洲培训20万名职业技术人才。
-
4月1日,中国导演协会2014年度奖提名揭晓,周冬雨、巩俐、梅婷、陶虹、桂纶镁同争影后,黄轩、陈道明、秦昊、廖凡、王景春同争影帝,姜文、张艺谋、娄烨、刁亦男、宁瀛被提名为年度导演,《一步之遥》《归来》《推拿》《白日焰火》《警察日记》角逐年度影片。
-
以根宝崇明基地球队为班底的绿地申花U19梯队去年为上海赢得过全运会U18男足的冠军,上港U19梯队几名主力也是全运冠军队的成员,那个全运冠军,是几家俱乐部的精英组成的,是集中上海该年龄层次优势兵力对抗全国,而青超联赛就是三家俱乐部拆分了,各队实力下降实属正常。
-
京衡律师上海事务所余超律师对北青报记者表示,经营者与消费者进行交易,应当遵循公开、公平、自愿、诚实信用的原则。经营者通过虚构事实、隐瞒真实情况的手段,用一个较高的价格欺骗、诱导特定的消费者与其进行交易,构成价格欺诈, 针对价格欺诈行为消费者有权向价格主管部门举报,由政府价格主管部门依照《中华人民共和国价格法》和《价格违法行为行政处罚规定》进行处罚。
-
很多人都有过这样的经历:由于发动机突然停止工作或是皮带松动,动力转向瞬间失灵。然而,这次的失误似乎不同于一般的“失灵”:或许是i30搭载了柔性转向系统(Flex Steer system)的原因,电力辅助系统让电阻值迅速增加,导致没有及时避开障碍物、测试失败;但是这也从一方面暴露了现代i30在安全技术上的缺陷。
-
而现在,她走出学术界,希望借助谷歌云端的力量,让技术可以真正走出去,转化成产品,传递给更多企业和个人应用,达到AI大众化的理想。
热点资讯
- 网购毒蛇被咬致死买卖双方、平台与快递谁来担责?2019-06-23
- 小米生态链出品,小白1080P智能摄像机云台版149元2019-06-23
- 《骑士的梦想》新服开启给你不得不玩的8大理由2019-06-23
- Android项目刮刮奖详解(三)2019-06-23
- 男人是娘还是Man:得看你小时候在什么环境长大2019-06-23
- 36个工作日后,中央银行重新启动反向回购业务,投资1600亿新浪财经。2019-06-23
- 国轩高科:目前生产经营正常订单充足2019-06-23
- 晋江经验:小村企大希望2019-06-23