Java線程安全性中的對象發佈和逸出

      發佈(Publish)和逸出(Escape)是Java併發編程中需要注意的問題。
      “發佈”,簡單來說就是提供一個對象的引用給作用域之外的代碼。比如return一個對象,或者作爲參數傳遞到其他類的方法中。
      “逸出”,是指如果一個類還沒有構造結束就已經提供給了外部代碼一個對象引用即發佈了該對象,此時叫做對象逸出,對象的逸出會破壞線程的安全性。

      下面代碼是我們經常編寫的代碼,states變量是一個私有變量,然而getStates()方法將states對象發佈,states逃出了其作用域,這樣其他類或對象就可以修改該對象:

@Slf4j
public class UnsafePublish {

    private String[] states = {"a", "b", "c"};

    public String[] getStates() {
        return states;
    }

    public static void main(String[] args) {
        UnsafePublish unsafePublish = new UnsafePublish();
        log.info("{}", Arrays.toString(unsafePublish.getStates()));

        unsafePublish.getStates()[0] = "d";
        log.info("{}", Arrays.toString(unsafePublish.getStates()));
    }
}

      運行代碼輸出:

[a, b, c]
[d, b, c]

      還有更加隱祕的this逸出,代碼如下:

@Slf4j
public class Escape {

    private int thisCanBeEscape = 0;

    public Escape () {
        new InnerClass();
    }

    private class InnerClass {

        public InnerClass() {
            log.info("{}", Escape.this.thisCanBeEscape);
        }
    }

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

      在上述代碼中,在Escape的構造函數中相當於創建了一個線程,創建了一個內部類對象,內部類裏面使用thisCanBeEscape,有可能在對象還沒有發佈完成就先使用了,有可能導致this引用在構造過程中逸出,這種對象也因此被認爲是不正確構造。

      如果要在構造器中創建線程,應該使用專有的start或初始化的方法來統一啓動線程,例如可以使用工廠方法和私有構造函數來完成對象的創建和監聽器的創建。

     

      安全發佈對象4種方法:

  • 在靜態初始化函數中初始化一個對象引用
  • 將對象的引用保存到volatile類型域或者AtmoicReference對象中
  • 將對象的引用保存到某個正確構造對象的final類型域中
  • 將對象的引用保存到一個由鎖保護的域中
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章