1,this引用逃逸

并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了。
这是危及到线程安全的,因为其他线程有可能通过这个逸出的引用访问到“初始化了一半”的对象(partially-constructed object)。
这样就会出现某些线程中看到该对象的状态是没初始化完的状态,而在另外一些线程看到的却是已经初始化完的状态,
这种不一致性是不确定的,程序也会因此而产生一些无法预知的并发错误。

补充:内部的特性:

内部类、匿名内部类都可以访问外部类的对象的域,为什么会这样,
实际上是因为内部类构造的时候,会把外部类的对象this隐式的作为一个参数传递给内部类的构造方法,这个工作是编译器做的,
所以下面例子里的匿名内部类在构造ThisEscape时就把ThisEscape创建的对象隐式的传给匿名内部类了。

1,1,this引用逸出是如何产生的

正如代码清单1所示,ThisEscape在构造函数中引入了一个内部类EventListener,而内部类会自动的持有其外部类(这里是ThisEscape)的this引用。
source.registerListener会将内部类发布出去,从而ThisEscape.this引用也随着内部类被发布了出去。
但此时ThisEscape对象还没有构造完成,id已被赋值为1,但name还没被赋值,仍然为null。

ps:简单来说就是,

在一个类的构造器创建了一个内部类(内部类本身是拥有对外部类的所有成员的访问权的),此时外部类的成员变量还没初始化完成。
但是,同时这个内部类被其他线程获取到,并且调用了内部类可以访问到外部类还没来得及初始化的成员变量的方法。

代码清单1 this引用逸出示例

public class ThisEscape {
public final int id;
public final String name;
public ThisEscape(EventSource<EventListener> source) {
id = 1;
source.registerListener(new EventListener() { //内部类是可以直接访问外部类的成员变量的(外部类引用this被内部类获取了)
public void onEvent(Object obj) {
System.out.println("id: "+ThisEscape.this.id);
System.out.println("name: "+ThisEscape.this.name);
}
});
name = "flysqrlboy";
} }

代码清单2 EventSource类:

public class EventSource<T> {
private final List<T> eventListeners ;
public EventSource() {
eventListeners = new ArrayList<T>() ;
} public synchronized void registerListener(T eventListener) { //数组持有传入对象的引用
this.eventListeners.add(eventListener);
this.notifyAll();
} public synchronized List<T> retrieveListeners() throws InterruptedException { //获取持有对象引用的数组
List<T> dest = null;
if(eventListeners.size() <= 0 ) {
this.wait();
}
dest = new ArrayList<T>(eventListeners.size()); //这里为什么要创建新数组,好处在哪里
dest.addAll(eventListeners);
return dest;
}
}

把内部类对象发布出去的source.registerListener语句没什么特殊的(发布其实就是让别的类有机会持有这个内部类的引用),

从代码清单2可发现,registerListener方法只是往list中添加一个EventListener元素而已。

这样,其他持有EventSource对象的线程从而持有EventListener对象,便可以访问ThisEscape的内部状态了(id和name)。

代码清单3中的ListenerRunnable 就是这样的线程:

public class ListenerRunnable implements Runnable {  

      private EventSource<EventListener> source;
public ListenerRunnable(EventSource<EventListener> source) {
this.source = source;
}
public void run() {
List<EventListener> listeners = null; try {
listeners = this.source.retrieveListeners();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(EventListener listener : listeners) {
listener.onEvent(new Object()); //执行内部类获取外部类的成员变量的方法
}
}
}

只要线程得到持有内部类引用的数组,就可以使用内部类获取外部类的有可能未初始化的成员变量。

代码清单4 ThisEscapeTest

public class ThisEscapeTest {  

      public static void main(String[] args) {
EventSource<EventListener> source = new EventSource<EventListener>();
ListenerRunnable listRun = new ListenerRunnable(source);
Thread thread = new Thread(listRun);
thread.start();
ThisEscape escape1 = new ThisEscape(source);
}
}

启动了一个ListenerRunnable 线程,用于监视ThisEscape的内部状态。

主线程紧接着调用ThisEscape的构造函数,新建一个ThisEscape对象。

在ThisEscape构造函数中,如果在source.registerListener语句之后,name="flysqrlboy"赋值语句之前正好发生上下文切换,

ListenerRunnable 线程就有可能看到了还没初始化完的ThisEscape对象,即id为1,但是name仍然为null!

1,2,另外一种就是在在构造函数中启动新的线程的时候,容易发生This逃逸。代码如下:

public class ThreadThisEscape {    
//成员变量xxx
public ThisEscape() {
new Thread(new EscapeRunnable()).start(); //使用未初始化的成员变量
// 初始化成员变量
} private class EscapeRunnable implements Runnable {
@Override
public void run() {
//使用成员变量
// ThreadThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸
}
}
}

1,3,如何避免this引用逸出

导致的this引用逸出需要满足两个条件:

一个是在构造函数中创建内部类(EventListener),
另一个是在构造函数中就把这个内部类给发布了出去(source.registerListener)。

因此,我们要防止这一类this引用逸出的方法就是避免让这两个条件同时出现。

也就是说,如果要在构造函数中创建内部类,那么就不能在构造函数中把他发布了,应该在构造函数外发布,即等构造函数执行完初始化工作,再发布内部类。

正如如下所示,使用一个私有的构造函数进行初始化和一个公共的工厂方法进行发布。

public class ThisSafe {  

      public final int id;
public final String name;
private final EventListener listener; private ThisSafe() {
id = 1;
listener = new EventListener(){
public void onEvent(Object obj) {
System.out.println("id: "+ThisSafe.this.id);
System.out.println("name: "+ThisSafe.this.name);
}
};
name = "flysqrlboy";
} public static ThisSafe getInstance(EventSource<EventListener> source) {
ThisSafe safe = new ThisSafe(); //先初始化
source.registerListener(safe.listener); //发布内部类
return safe;
}

2,联想到构造器没有初始化完成就调用方法的情况。

(构造器是可以调用方法初始化变量的)

在父类构造函数内部调用具有多态行为的函数将导致无法预测的结果,因为此时子类对象还没初始化。

class Glyph {
void draw() { //没有执行
System.out.println("Glyph.draw()");
}
Glyph() { //3,默认调用
System.out.println("Glyph() before draw()");
draw(); //父类构造器作为子类构造器执行前的默认执行,此时父构造器内执行的方法是子类的重写方法
System.out.println("Glyph() after draw()");
}
} class RoundGlyph extends Glyph {
private int radius = 1; //5,初始化变量 RoundGlyph(int r) {//2,首先调用父类构造器(并且默认是无参构造器)
radius = r; //6,赋值执行
System.out.println("RoundGlyph.RoundGlyph(). radius = " + radius);
} void draw() { //4,在父构造器被调用,此时该类(子类)还没被初始化,所以实例变量的值为默认值。
System.out.println("RoundGlyph.draw(). radius = " + radius);
}
} public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);//1,首先执行
}}

输出:

Glyph() before draw()
RoundGlyph.draw(). radius = 0 //未被初始化
Glyph() after draw()
RoundGlyph.RoundGlyph(). radius = 5

为什么会这样输出?这就要明确掌握Java中构造函数的调用顺序

(1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0;
(2)调用基类构造函数。从根开始递归下去,因为多态性此时调用子类覆盖后的draw()方法(要在调用RoundGlyph构造函数之前调用),
由于步骤1的缘故,我们此时会发现radius的值为0;
(3)按声明顺序调用成员的初始化方法;
(4)最后调用子类的构造函数。

3,子类何时调用父类构造器

子类总是会调用父类构造器,之所以需要调用父类的构造方法是因为在父类中,可能存在私有属性需要在其构造方法内初始化;

调用的情况情况:

1,默认情况,子类总是会调用父类默认无参构造器。
2,在子类构造器中指定需要调用的父类构造器(有/无参都可以),并且必须在子类的构造器中的第一行位置。
3,子类存在多个构造器,如果嵌套使用:(PS:编译期会合并其中的嵌套构造器)
3,1,合并后,默认也是调用父类的无参构造器。 
3,2,子类指定父类构造器,这时指定的父类构造器逻辑上在子类构造器的首行就好,因为会合并。

ps:19行中嵌套21行的构造器,经过编译期合并处理,父构造器仍然是在第一行中。

补充:

1,如果没有自定义构造器,编译期会默认为类添加无参构造器。
2,构造器的执行并不会创建对象,只有new+构造器的组合语句,才表示创建对象。

this引用逃逸(使用内部类获取未外部类未初始化完的变量),多态父类使用子类未初始化完的变量的更多相关文章

  1. 牛客网Java刷题知识点之什么是内部类、为什么要使用内部类、内部类如何使用外部类的属性和方法、成员内部类、局部内部类、静态内部类、匿名内部类

    不多说,直接上干货! 可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 内部类 ...

  2. 深入理解Java中为什么内部类可以访问外部类的成员

    内部类简介 虽然Java是一门相对比较简单的编程语言,但是对于初学者, 还是有很多东西感觉云里雾里, 理解的不是很清晰.内部类就是一个经常让初学者感到迷惑的特性. 即使现在我自认为Java学的不错了, ...

  3. Android(java)学习笔记150:为什么局部内部类只能访问外部类中的 final型的常量

    为什么匿名内部类参数必须为final类型: 1)  从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变 ...

  4. Java中内部类揭秘(一):外部类与非静态内部类的”相互可见性“

               声明:本博客为原创博客.未经同意,不得转载.原文链接为 http://blog.csdn.net/bettarwang/article/details/27012421.     ...

  5. Android(java)学习笔记93:为什么局部内部类只能访问外部类中的 final型的常量

    为什么匿名内部类参数必须为final类型: 1)  从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变 ...

  6. 141、Java内部类之实例化外部类对象

    01. 代码如下: package TIANPAN; class Outer { // 外部类 private static String msg = "Hello World !" ...

  7. java——多线程——内部类共享同一个外部类对象的成员变量

    public class Shop { public static void main(String[] args) { Outer o=new Outer(); Thread t1=o.getSal ...

  8. 内部类访问外部类的变量必须是final吗,java静态方法中不能引用非静态变量,静态方法中不能创建内部类的实例

    内部类访问外部类的变量必须是final吗? 如下: package com.java.concurrent; class A { int i = 3; public void shout() { cl ...

  9. this引用逃逸问题

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

随机推荐

  1. [Jmeter]Xpath获取元素某个属性的值,以及获取最后一个元素某个属性的值

    XPath获取元素某个属性的值 XPath query:  clients/attribute::total XPath获取最后一个元素某个属性的值 XPath query:   /clients/c ...

  2. 抽象 abstract 和 接口 interface。 java 的 堆 和 栈。 参数传递(基本类型和普通对象的区别)

    package com.test; import com.test.Pro; //protected 修饰的成员只能在本包中和 继承该类的方法中使用 public abstract class Tes ...

  3. 第八章 连词(Les conjonction )

    ★并列连词(La conjonction de coordination ) ()表示联合关系的并列连词 .et连接肯定的内容.如:    ➞Il conduit vite et bien.      ...

  4. Netty学习第四节WebSocket入门

    1.什么是webSocket?       webSocket是H5提出的一个协议规范,webSocket约定了一个通信的规范,通过一个握手的机制,客户端和服务端,就能建立一个类似TCP的连接,从而方 ...

  5. IntelliJ IDEA 2017版 编译器使用学习笔记(二) (图文详尽版);IDE快捷键使用

    补充介绍IntellJ 介绍主菜单功能及相关用途: File -------------> 对文件进行操作 Edit ------------> 对文本进行操作 View -------- ...

  6. (深搜)Sum It Up -- poj --1564

    链接: http://poj.org/problem?id=1564 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=88230#probl ...

  7. hdu 4993

    http://acm.hdu.edu.cn/showproblem.php?pid=4993 满足ax + by = c的x,y对数 水题,暴力 #include <cstdio> #in ...

  8. Elasticsearch 健康状态处理

    笔者在自己的 ubuntu 服务器上使用 GET /_cat/health?v 命令时,返回值如下所示 可以看到集群状态为 yellow,这是什么意思呢?原来在 es 的集群状态中,有三种情况,官网描 ...

  9. Thread in depth 2:Asynchronization and Task

    When we want to do a work asynchronously, creating a new thread is a good way. .NET provides two oth ...

  10. MySQL Route负载均衡与读写分离Docker环境使用

    Docker环境描述 主机名 部署服务 备注 MySQL Route MySQL Route 部署在宿主机上的MySQL Route服务 MySQL Master1 MySQL 5.7.16 Dock ...