1、什么是This逃逸?

  在构造器构造还未彻底完成前(即实例初始化阶段还未完成),将自身this引用向外抛出并被其他线程复制(访问)了该引用,可能会问到该还未被初始化的变量,甚至可能会造成更大严重的问题。

  废话不多说,看一下代码

 /**
* 模拟this逃逸
* @author Lijian
*
*/
public class ThisEscape {
//final常量会保证在构造器内完成初始化(但是仅限于未发生this逃逸的情况下,具体可以看多线程对final保证可见性的实现)
final int i;
//尽管实例变量有初始值,但是还实例化完成
int j = 0;
static ThisEscape obj;
public ThisEscape() {
i=1;
j=1;
//将this逃逸抛出给线程B
obj = new ThisEscape();
}
public static void main(String[] args) {
//线程A:模拟构造器中this逃逸,将未构造完全对象引用抛出
/*Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//obj = new ThisEscape();
}
});*/
//线程B:读取对象引用,访问i/j变量
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
//可能会发生初始化失败的情况解释:实例变量i的初始化被重排序到构造器外,此时1还未被初始化
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未被初始化");
}
}
});
//threadA.start();
threadB.start();
}
}

输出结果:这说明ThisEscape还未完成实例化,构造还未彻底结束。

发生空指针错误:普通变量j未被初始化
发生空指针错误:final变量i未被初始化

另一种情况是利用线程A模拟this逃逸,但不一定会发生,线程A模拟构造器正在构造...而线程B尝试访问变量,这是因为

(1)由于JVM的指令重排序存在,实例变量i的初始化被安排到构造器外(final可见性保证是final变量规定在构造器中完成的);

(2)类似于this逃逸,线程A中构造器构造还未完全完成。

所以尝试多次输出(相信我一定会发生的,只是概率相对低),也会发生类似this引用逃逸的情况。

 /**
* 模拟this逃逸
* @author Lijian
*
*/
public class ThisEscape {
//final常量会保证在构造器内完成初始化(但是仅限于未发送this逃逸的情况下)
final int i;
//尽管实例变量有初始值,但是还实例化完成
int j = 0;
static ThisEscape obj;
public ThisEscape() {
i=1;
j=1;
//obj = new ThisEscape();
}
public static void main(String[] args) {
//线程A:模拟构造器中this逃逸,将未构造完全对象引用抛出
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//构造初始化中...线程B可能获取到还未被初始化完成的变量
//类似于this逃逸,但并不定发生
obj = new ThisEscape();
}
});
//线程B:读取对象引用,访问i/j变量
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
//可能会发生初始化失败的情况解释:实例变量i的初始化被重排序到构造器外,此时1还未被初始化
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未被初始化");
}
}
});
threadA.start();
threadB.start();
}
}

2、什么情况下会This逃逸?

(1)在构造器中很明显地抛出this引用提供其他线程使用(如上述的明显将this抛出)。

(2)在构造器中内部类使用外部类情况:内部类访问外部类是没有任何条件的,也不要任何代价,也就造成了当外部类还未初始化完成的时候,内部类就尝试获取为初始化完成的变量

  • 在构造器中启动线程:启动的线程任务是内部类,在内部类中xxx.this访问了外部类实例,就会发生访问到还未初始化完成的变量
  • 在构造器中注册事件,这是因为在构造器中监听事件是有回调函数(可能访问了操作了实例变量),而事件监听一般都是异步的。在还未初始化完成之前就可能发生回调访问了未初始化的变量。

在构造器中启动线程代码实现:

 /**
* 模拟this逃逸2:构造器中启动线程
* @author Lijian
*
*/
public class ThisEscape2 {
final int i;
int j;
public ThisEscape2() {
i = 1;
j = 1;
new Thread(new RunablTest()).start();
}
//内部类实现Runnable:引用外部类
private class RunablTest implements Runnable{
@Override
public void run() {
try {
System.out.println(ThisEscape2.this.j);
} catch (NullPointerException e) {
System.out.println("发生空指针错误:普通变量j未被初始化");
}
try {
System.out.println(ThisEscape2.this.i);
} catch (NullPointerException e) {
System.out.println("发生空指针错误:final变量i未被初始化");
}
} }
public static void main(String[] args) {
new ThisEscape2();
}
}

构造器中注册事件,引用网上的一段伪代码将以解释:

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引用的时候需要特别注意!

this引用逃逸的更多相关文章

  1. this引用逃逸(使用内部类获取未外部类未初始化完的变量),多态父类使用子类未初始化完的变量

    1,this引用逃逸 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了. 这是危及到线程安全的,因为其他线程有可能 ...

  2. this引用逃逸问题

    //this引用逃逸 // 1.构造器还未完成前,将自身this引用向外抛,使其他线程访问这个引用,进而访问到其未初始化的变量,造成问题 // 2.内部类访问外部类未初始化的成员变量 //3.多态继承 ...

  3. 你不知道的Java引用

    什么是引用   引用就是保存着一块地址(门牌号)的对象,就像C语言的指针那样,引用可以传递某个数据的地址,如果我们想拿到某一条数据,就要先找到他的地址,然后告诉计算机我去拿这个地址的数据,最后计算机就 ...

  4. 浅析java内存模型--JMM(Java Memory Model)

    在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...

  5. java并发编程(十七)内存操作总结

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...

  6. Java的线程安全

    线程安全 我们这里讨论的线程安全,就限定于多个线程之间存在共享数据访问这个前提,因为如果一段代码根本不会与其他线程共享数据,那么从线程安全的角度来看,程序是串行执行还是多线程执行对它来说是完全没有区别 ...

  7. java gc的工作原理、如何优化GC的性能、如何和GC进行有效的交互

    java gc的工作原理.如何优化GC的性能.如何和GC进行有效的交互 一个优秀的Java 程序员必须了解GC 的工作原理.如何优化GC的性能.如何和GC进行有效的交互,因为有一些应用程序对性能要求较 ...

  8. Java的内存模型

    "让计算机并发执行若干个运算任务"与"更充分地利用计算机处理器的效能"之间的因果关系,看起来顺理成章,实际上它们之间的关系并没有想象中的那么简单,其中一个重要的 ...

  9. 转《深入理解Java虚拟机》学习笔记之最后总结

    编译器 Java是编译型语言,按照编译的时期不同,编译器可分为: 前端编译器:其实叫编译器的前端更合适些,它把*.java文件转变成*.class文件,如Sun的Javac.Eclipse JDT中的 ...

随机推荐

  1. mysql的部署

    mysql在linux系统中的部署: 二进制包安装软件: 第一步:下载二进制软件,上传到服务器 www.mysql.com mkdir /server/tools -y cd /server/tool ...

  2. Python开发——5.函数

    一.函数的定义 def test(x) "The Function definitions" x += return x def:定义函数的关键字 test:函数名 ():定义形参 ...

  3. 2019年微服务5大趋势,你pick哪个?

    2018年对于微服务来说是非常重要的一年,这一年Service Mesh开始崭露头角,解决服务间复杂的通信问题,这一年很多国内互联网公司已经有了较为成熟的微服务实践案例,网易云主办的微服务实践沙龙中也 ...

  4. 项目Alpha冲刺(团队5/10)

    项目Alpha冲刺(团队5/10) 团队名称: 云打印 作业要求: 项目Alpha冲刺(团队) 作业目标: 完成项目Alpha版本 团队队员 队员学号 队员姓名 个人博客地址 备注 221600412 ...

  5. jdk8新特性---list.stream

    项目中用到了该api ,记录下来 具有get set 构造方法的实体类 开始使用: 结果为: 更多可以参考: https://blog.csdn.net/justloveyou_/article/de ...

  6. 第二十二节:Java语言基础-详细讲解位运算符与流程控制语句

    位运算符(二进制位运算) 运算符 运算 例子 << 左移 3 << 2 = 12 --> 3 * 2 * 2 =12 >> 右移 3 >> 1 = ...

  7. react在router中传递数据的2种方法

    概述 不传递数据叫什么单页面应用,渲染模块还需要http请求算什么单页面应用. 本文总结了react-router4中使用BrowserRouter时传递数据的两种方法,供以后开发参考,相信对其他人也 ...

  8. 机器学习基石笔记:09 Linear Regression

    线性回归假设: 代价函数------均方误差: 最小化样本内代价函数: 只有满秩方阵才有逆矩阵. 线性回归算法流程: 线性回归算法是隐式迭代的. 线性回归算法泛化可能的保证: 根据矩阵的迹的性质:tr ...

  9. LeetCode:149_Max Points on a line | 寻找一条直线上最多点的数量 | Hard

    题目:Max Points on a line Given n points on a 2D plane, find the maximum number of points that lie on ...

  10. 外媒:比特币大陆将于9月IPO 规模或高达180亿美元

    看看你们坚持买的比特币是否值得? 北京时间8月13日上午消息,据CoinDesk获得的文件,比特币大陆将于今年9月申请首次公开募股(IPO),其规模可能高达180亿美元,市值预计在400亿美元到500 ...