`

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

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

    Java并发编程规则:不可变对象永远是线程安全的.docx

    一个不可变的对象必须满足的条件:它的状态在创建后不能再修改,所有域都是final类型,并且它被正确创建(创建期间没有发生this引用的逸出)。 不可变对象的优点在于它们可以被安全地共享在多个线程之间,不需要...

    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 ...

    vue面试题第二部分整理

    Vue面试题第二部分主要涵盖了JavaScript基础到高级的概念,这些知识点对于理解...以上知识点是Vue面试中常见的JavaScript基础问题,掌握这些知识能够帮助开发者更好地理解Vue.js的运行机制,并在面试中表现出专业水平。

    2013高考英语 易错题查漏补缺 完形填空精选练习(39)

    4. “gas”指的是从作者房间窗户逸出的气体,可能是二氧化碳等温室气体,这是导致全球变暖的一个因素。 5. “Don’t think the messy state of my room doesn’t bother me, it does.” 表明虽然作者用轻松的语气...

    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.

    jquery实现点赞效果

    if ($(this).hasClass("liked")) { // 用户已点赞,取消点赞 $(this).removeClass("liked"); likeCount--; } else { // 用户未点赞,进行点赞 $(this).addClass("liked"); likeCount++; } // 更新点赞计数...

    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...

    java并发编程实践笔记

    14. **对象发布的注意事项:** 发布对象时必须确保其完全构造完成,以防止对象逸出导致的线程安全问题。 15. **使用ThreadLocal确保线程封闭性:** 通过`ThreadLocal`可以在每个线程中存储独立的副本,从而避免线程...

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

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

    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提供的预训练权重

    NASNet-mobile.h5

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

    Java基础面试题200.docx

    答:Java 中实现多线程主要有两种方式:1)继承 Thread 类,重写 run() 方法,并通过 new Thread(this).start() 创建和启动线程;2)实现 Runnable 接口,实现 run() 方法,然后通过创建 Thread 对象并将 Runnable ...

    java窗体风格

    4. **异常处理**:由于`setLookAndFeel`方法可能会抛出几种不同的异常,因此需要对其进行捕获并处理。 #### 四、扩展与实践 除了以上提到的预定义风格外,Swing还允许开发人员自定义外观与感觉。这通常涉及创建...

    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 ...

    Qt 波形图表

    connect(worker, &WorkerObject::dataReady, this, &MainWindow::updateChart); // 数据准备好后通知主线程 connect(thread, &QThread::started, worker, &WorkerObject::startProcessing); // 开始处理数据 connect...

Global site tag (gtag.js) - Google Analytics