`

this 逸出

    博客分类:
  • java
 
阅读更多
转自:http://blog.csdn.net/flysqrlboy/article/details/10607295?reload
 
 
并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了。这是危及到线程安全的,因为其他线程有可能通过这个逸出的引用访问到“初始化了一半”的对象(partially-constructed object)。这样就会出现某些线程中看到该对象的状态是没初始化完的状态,而在另外一些线程看到的却是已经初始化完的状态,这种不一致性是不确定的,程序也会因此而产生一些无法预知的并发错误。在说明并发编程中如何避免this引用逸出之前,我们先看看一个对象是如何产生this引用逸出的。
 
一、this引用逸出是如何产生的
     正如代码清单1所示,ThisEscape在构造函数中引入了一个内部类EventListener,而内部类会自动的持有其外部类(这里是ThisEscape)的this引用。source.registerListener会将内部类发布出去,从而ThisEscape.this引用也随着内部类被发布了出去。但此时ThisEscape对象还没有构造完成 —— id已被赋值为1,但name还没被赋值,仍然为null。
          
     代码清单1 this引用逸出示例
 
[java] view plaincopy
 
  1. public class ThisEscape {  
  2.   
  3.       public final int id;  
  4.       public final String name;  
  5.       public ThisEscape(EventSource<EventListener> source) {  
  6.             id = 1;  
  7.             source.registerListener(new EventListener() {  
  8.                   public void onEvent(Object obj) {  
  9.                         System.out.println("id: "+ThisEscape.this.id);  
  10.                         System.out.println("name: "+ThisEscape.this.name);  
  11.                   }  
  12.             });  
  13.             name = "flysqrlboy";  
  14.               
  15.       }  
  16.   }  
 
实际上,清单1中把内部类对象发布出去的source.registerListener语句没什么特殊的,从代码清单2可发现,registerListener方法只是往list中添加一个EventListener元素而已。这样,其他持有EventSource对象从而持有EventListener对象的线程,便可以访问ThisEscape的内部状态了(id和name)。代码清单3中的ListenerRunnable 就是这样的线程。
 
代码清单2 EventSource类
 
[java] view plaincopy
 
  1. public class EventSource<T> {  
  2.   
  3.       private final List<T> eventListeners ;  
  4.         
  5.       public EventSource() {  
  6.             eventListeners = new ArrayList<T>() ;  
  7.       }  
  8.         
  9.       public synchronized void registerListener(T eventListener) {  
  10.             this.eventListeners.add(eventListener);  
  11.             this.notifyAll();  
  12.       }  
  13.         
  14.       public synchronized List<T> retrieveListeners() throws InterruptedException {  
  15.             List<T> dest = null;  
  16.             if(eventListeners.size() <= 0 ) {  
  17.                   this.wait();  
  18.             }  
  19.             dest = new ArrayList<T>(eventListeners.size());  
  20.             dest.addAll(eventListeners);  
  21.             return dest;  
  22.       }  
  23.   }  
代码清单3 ListenerRunnable 类
 
[java] view plaincopy
 
  1. public class ListenerRunnable implements Runnable {  
  2.   
  3.       private EventSource<EventListener> source;  
  4.       public ListenerRunnable(EventSource<EventListener> source) {  
  5.             this.source = source;  
  6.       }  
  7.       public void run() {  
  8.             List<EventListener> listeners = null;  
  9.               
  10.             try {  
  11.                   listeners = this.source.retrieveListeners();  
  12.             } catch (InterruptedException e) {  
  13.                   // TODO Auto-generated catch block  
  14.                   e.printStackTrace();  
  15.             }  
  16.             for(EventListener listener : listeners) {  
  17.                   listener.onEvent(new Object());  
  18.             }  
  19.       }  
  20.   
  21.   }  
      
        代码清单4是个普通的消费线程的客户端程序,它先启动了一个ListenerRunnable 线程,用于监视ThisEscape的内部状态。紧接着调用ThisEscape的构造函数,新建一个ThisEscape对象。在ThisEscape构造函数中,如果在source.registerListener语句之后,name="flysqrlboy"赋值语句之前正好发生上下文切换(如图1),ListenerRunnable 线程就有可能看到了还没初始化完的ThisEscape对象--即id为1,但是name仍然为null!虽然正好在这个点上发生上下文切换是“偶然”事件,但理论上它是存在的。而这正是并发编程令人头疼的地方--平时好好的,但有时候就莫名其妙的失败了!而且还很难找出原因。为了使本例的this引用逸出容易被观察到,我们试图改造一下ThisEscape的构造函数(代码清单5),假设在source.registerListener和name赋值语句之间,还有其他的初始化操作,而且是比较耗时的。我们用一个sleep方法来模拟这样的耗时操作。经过这样的改造后,this引用逸出几乎是必然出现的--id等于1,name等于null。

代码清单4 ThisEscapeTest
 
 
 
[java] view plaincopy
 
  1. public class ThisEscapeTest {  
  2.   
  3.       public static void main(String[] args) {  
  4.             EventSource<EventListener> source = new EventSource<EventListener>();  
  5.             ListenerRunnable listRun = new ListenerRunnable(source);  
  6.             Thread thread = new Thread(listRun);  
  7.             thread.start();  
  8.             ThisEscape escape1 = new ThisEscape(source);  
  9.       }  
  10. }  
 
图1 上下文切换
 


 
代码清单5 改造后的ThisEscape
 
[java] view plaincopy
 
  1. public class ThisEscape {  
  2.   
  3.       public final int id;  
  4.       public final String name;  
  5.       public ThisEscape(EventSource<EventListener> source) {  
  6.             id = 1;  
  7.             source.registerListener(new EventListener() {  
  8.                   public void onEvent(Object obj) {  
  9.                         System.out.println("id: "+ThisEscape.this.id);  
  10.                         System.out.println("name: "+ThisEscape.this.name);  
  11.                   }  
  12.             });  
  13.             try {  
  14.                   Thread.sleep(1000); // 调用sleep模拟其他耗时的初始化操作  
  15.             } catch (InterruptedException e) {  
  16.                   // TODO Auto-generated catch block  
  17.                   e.printStackTrace();  
  18.             }  
  19.             name = "flysqrlboy";  
  20.               
  21.       }  
  22. }  

二、如何避免this引用逸出
        上文演示了由内部类导致的this引用逸出是怎样产生的。它需要满足两个条件:一个是在构造函数中创建内部类(EventListener),另一个是在构造函数中就把这个内部类给发布了出去(source.registerListener)。因此,我们要防止这一类this引用逸出的方法就是避免让这两个条件同时出现。也就是说,如果要在构造函数中创建内部类,那么就不能在构造函数中把他发布了,应该在构造函数外发布,即等构造函数执行完毕,初始化工作已全部完成,再发布内部类。正如清单6所示的那样,使用一个私有的构造函数进行初始化和一个公共的工厂方法进行发布。
 
       代码清单6 安全的构建以防止this引用逸出
 
[java] view plaincopy
 
  1. public class ThisSafe {  
  2.   
  3.       public final int id;  
  4.       public final String name;  
  5.       private final EventListener listener;  
  6.         
  7.       private ThisSafe() {  
  8.             id = 1;  
  9.             listener = new EventListener(){  
  10.                   public void onEvent(Object obj) {  
  11.                         System.out.println("id: "+ThisSafe.this.id);  
  12.                         System.out.println("name: "+ThisSafe.this.name);  
  13.                   }  
  14.             };  
  15.             name = "flysqrlboy";  
  16.       }  
  17.         
  18.       public static ThisSafe getInstance(EventSource<EventListener> source) {  
  19.             ThisSafe safe = new ThisSafe();  
  20.             source.registerListener(safe.listener);  
  21.             return safe;  
  22.       }  
  23.         
  24.         
  25. }  
       
       另一种导致this引用逸出的常见错误,是在构造函数中启动一个线程。其原理跟上文说的内部类导致的this引用逸出相类似。解决的办法也相似,即可以在构造函数中创建线程,但别启动它。在构造函数外面再启动。
  • 大小: 24.8 KB
分享到:
评论

相关推荐

    华南理工大学物理实验报告-金属逸出功实验

    金属中存在大量的自由电子,但电子在金属内部所具有的能量低于在外部所具有的 能量,因而电子逸出金属时需要给电子提供一定的能量,这部分能量称为电子逸出功。 逸出功(功函)是金属材料基本属性之一,金属逸出功是...

    Javaswing登录页面的实现

    import java.awt.BorderLayout;... if (this.vcode.getCode().equals(this.jt_code.getText())) { return true; } return false; } public static void main(String[] args) { new LoginFjame(); } }

    02. 英语音标讲义-2(15页).doc

    [ʃ] [Ʒ]舌尖和舌端抬向上齿龈较后部分,舌身两侧紧贴上颚,中央形成一条狭长的通道,上下齿靠拢或靠近,但不要咬住,气流由舌端与上齿龈较后部分之间逸出,摩擦成音。  [Ɵ] [ð] 舌尖轻触上齿背,气流由舌齿间的...

    Context-Based Chinese Word Segmentation without Dictionary Support

    This paper presents a new machine-learning Chinese word segmentation (CWS) approach, which defines CWS as a break-point classifi- cation problem; the break point is the bound- ary of two subsequent ...

    500 Lines or Less.zip

    500 Lines or Less focuses on the design decisions that programmers make in the small when they are ... The programs you will read about in this book were all written from scratch for this purpose.

    halcon入门

    This section introduces you to HALCON’s matching functionality. In particular, it provides you with an overview on • how to use this manual (section 1.1), • the general meaning of matching (section...

    densenet121-weights-tf-dim-ordering-tf-kernels.h5

    model weight in this repo https://github.com/fchollet/deep-learning-models Keras提供的预训练权重

    JAVA线程程序设计(小时钟)实验报告(附完整代码).doc

    二、设计阵哼赤贫烹窖炉银稀硕匣趣奋锻换示哮拳染恤刁杨纺意卖磋卫者憾盟末蘸矗鸵 佩纪缝初陵鸯咸捌鄂潘苞遗衙睛罩贪煽跺栖躯梗赶酷帅秤车眉咆银薯粒烛侧端技言钾伏 棠篆私着梦肆现逸卒牡猜楞哦侮淘吁痴蚁昭串痹孔...

    inception_resnet_v2_weights_tf_dim_ordering_tf_kernels_notop.h5

    model weight in this repo https://github.com/fchollet/deep-learning-models Keras提供的预训练权重

    densenet201_weights_tf_dim_ordering_tf_kernels.h5

    model weight in this repo https://github.com/fchollet/deep-learning-models Keras提供的预训练权重

    densenet169_weights_tf_dim_ordering_tf_kernels.h5

    model weight in this repo https://github.com/fchollet/deep-learning-models Keras提供的预训练权重

    vue面试题第二部分整理

    9.This 10.async、await优缺点 11.generator原理 12.Promise 13.如何实现一个Promise 14.==与===的区别 15.基本数据类型和引用类型在存储上的差别 16.浏览器Eventloop和Node中有什么区别 17.setTimeout倒计时误差 18...

    NASNet-mobile.h5

    model weight in this repo https://github.com/fchollet/deep-learning-models Keras提供的预训练权重

    C++.Concurrency.in.Action

    As a guide and reference to the new concurrency features in the upcoming C++ Standard and TR2, this book is invaluable for existing programmers familiar with writing multi-threaded code in C++ using ...

    Practical Augmented Reality

    This unique book is an easy-to-follow guide on how to do it. Guides you through the emerging technology of Augmented Reality (AR) Shows you how to use 3D data with the Processing programming ...

Global site tag (gtag.js) - Google Analytics