天天看點

并發程式設計-08安全釋出對象之釋出與逸出

文章目錄

  • 腦圖
  • 概念
  • 示例
  • 不安全的釋出對象Demo
  • 對象逸出Demo
  • 小結
  • 代碼
并發程式設計-08安全釋出對象之釋出與逸出

腦圖

并發程式設計-08安全釋出對象之釋出與逸出

概念

釋出對象: 使一個對象能夠被目前範圍之外的代碼所使用,日常開發中比較常見的比如通過類的非私有方法傳回對象的引用,或者通過公有的靜态變量釋出對象 等都屬于釋出對象

對象逸出: 首先需要明确的是對象逸出是一種錯誤的釋出方式。 當一個對象還沒有構造完成時,就使它被其他線程所見。

示例

不安全的釋出對象Demo

package com.artisan.example.publish;


import com.artisan.anno.NotThreadSafe;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@NotThreadSafe
public class UnSafePublishObjectDemo {

  // 私有變量
  private String name = "artisan";
  
  // 通過public通路級别的方法getName釋出了類的域,在類的外部,任何線程都可以通路這個域
  // 這樣釋出的對象是不安全的,因為我們無法得知其他線程是否會修改這個域導緻該類裡資料的錯誤
  public String  getName() {
    return name;
  }
  
  
  public static void main(String[] args) {
    
    // 通過new執行個體化UnSafePublishObjectDemo
    UnSafePublishObjectDemo unSafePublishObjectDemo = new UnSafePublishObjectDemo();
    // 調用getName()方法得到私有屬性的引用
    String name = unSafePublishObjectDemo.getName();
    log.info("name:{}",name);
    
    // 假設有第二個線程去修改name屬性的值
    String name2 = unSafePublishObjectDemo.getName();
    name2 = "小工匠";
    log.info("name:{}",name2);
    
  }
  
}      

上面的代碼裡,通過new對象初始化了​

​UnSafePublishObjectDemo​

​​對象。然後調用​

​getName()​

​方法擷取到了私有屬性的引用,這樣就可以在其他任何線程中,修改該屬性的值。這樣将會導緻我們在其他線程中,擷取該屬性的值時是不确定的,因為并不能得知該屬性的值是否已被其他線程所修改過,是以這就是不安全的對象釋出。

對象逸出Demo

package com.artisan.example.publish;

import com.artisan.anno.NotRecommand;
import com.artisan.anno.NotThreadSafe;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 *  對象逸出示例,在對象構造完成之前,不可以将其釋出
 * @author yangshangwei
 *
 */
@Slf4j
@NotThreadSafe
@NotRecommand
public class ObjectEscapeDemo {
    
    private int thisCanBeEscape = 0;

    public ObjectEscapeDemo() {
        new InnerClass();
    }

    private class InnerClass {
        // this引用的逸出
        // 内部類的構造器裡包含了對封裝執行個體的隐含引用,這樣在對象沒有被正确構造完成之前就會被釋出,由此會導緻不安全的因素在裡面
        public InnerClass() {
            log.info("{}", ObjectEscapeDemo.this.thisCanBeEscape);
        }
    }

    public static void main(String[] args) {
        new ObjectEscapeDemo();
    }

}      

上述代碼中,内部類的構造器裡包含了對封裝執行個體的隐含引用,這樣在對象沒有被正确構造完成之前就會被釋出,由此會導緻不安全的因素在裡面。

其中一個就是導緻this引用在構造期間逸出的錯誤,它是在構造函數構造過程中啟動了一個線程,無論是顯式啟動還是隐式啟動,都會造成this引用的逸出。

新線程總會在所屬對象構造完畢之前就已經看到它了,是以如果要在構造函數中建立線程,那麼不要啟動它,而是應該采用一個專有的start,或是其他初始化的方式統一啟動線程。

這裡其實我們可以使用工廠方法和私有構造函數來完成對象建立和監聽器的注冊等等來避免不正确的釋出。

小結

不正确的釋出可變對象導緻的兩種錯誤:

  • 釋出線程以外的所有線程都可以看到被釋出對象的過期的值
  • 線程看到的被釋出對象的引用是最新的,然而被釋出對象的狀态卻是過期的

代碼

繼續閱讀