title:this逃逸
date:2017年11月11日18:17:51
我们在说Java内存模型的时候提到了final域在初始化保证线程安全的相关特性。里面有句话是这样说的:
被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程中就能看到final字段的值,而且其外、外部可见状态永远也不会改变。它所带来的安全性是最简单最纯粹的。
关于fina域的使用,我们在讲DCL(双重检查加锁)的时候讲过,可以将成员变量定义为final修饰的,这样就能让其他线程看到构造方法中能看到构造方中设置的值。在java5以及之后的版本,ifinal变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正确运行了。
我们注意到,他这里强调了前提是构造函数中没有泄露this引用。那么什么是泄露this引用呢,泄露this引用即发生this逃逸:当内部类代码执行的时候,外部类对象的创建过程很有可能还没结束,这个时候如果内部类访问外部类中的数据,很有可能得到还没有正确初始化的数据。
我们来看一实例:
package com.wangcc.MyJavaSE.thread.escape;
/**
* @ClassName: EscapeTest
* @Description:在构造函数中使用内部类,并且代码可能会出现并行。也就是说,当内部类代码执行的时候,外部类对象的创建过程很有可能还没结束,这个时候如果内部类访问外部类中的数据,很有可能得到还没有正确初始化的数据。
* @author wangcc
* @date 2017年11月11日 下午6:08:36
*
*/
public class EscapeTest {
private int weight = 0;
public EscapeTest() {
new Thread(new EscapeRunnable()).start();
// 模拟构造函数耗时
for (int i = 0; i < 1000000; i++) {
}
weight = 1;
}
private class EscapeRunnable implements Runnable {
public void run() {
System.out.println(EscapeTest.this.weight);
}
}
public static void main(String[] args) {
new EscapeTest();
}
}
运行代码,得到的输出会是0,是外部类的默认值而不是构造方法中所赋的值。
我们可以看到:在构造函数返回之前其他线程就持有该对象的引用,调用尚未构造完全的对象的方法可能引发错误。这里跟DCL引发的问题很相似,DCL问题的出现是我们误认为new Instance是一个原子操作,而其实不是,又因为JVM的指令重排,所以会导致先返回对象应用再进行初始化工作,导致出错