java 發佈和逸出

【轉載】:http://www.2cto.com/kf/201310/247738.html

前言 

多線程併發環境下,線程安全極爲重要。往往一些問題的發生都是由於不正確的發佈了對象造成了對象逸出而引起的,因此如果系統開發中需要發佈一些對象,必須要做到安全發佈,以免造成安全隱患。
 
 
發佈和逸出 
    所謂發佈對象是指使一個對象能夠被當前範圍之外的代碼所使用。所謂逸出是指一種錯誤的發佈情況,當一個對象還沒有構造完成時,就使它被其他線程所見,這種情況就稱爲對象逸出。在我們的日常開發中,經常要發佈一些對象,比如通過類的非私有方法返回對象的引用,或者通過公有靜態變量發佈對象。如下面這些代碼所示:
 class Unsafepublish { 
       private String[] states={"AK","AL"};
       public String[] getStates(){  
           return states;
}
   publicstaticvoid main(String[] args) {
       UnSafeStates safe = newUnSafeStates();
       System.out.println(Arrays.toString(safe.getStates()));
       safe.getStates()[1] = "c";
       System.out.println(Arrays.toString(safe.getStates()));
   }
}
輸出結果[AL,KL] 
     [AL,c]
    以上代碼通過public訪問級別發佈了類的域,在類的外部任何線程都可以訪問這些域,這樣發佈對象是不安全的,因爲我們無法假設,其他線程不會修改這些域,從而造成類狀態的錯誤。還有一種逸出是在構造對象時發生的,它會使類的this引用發生逸出,從而使線程看到一個構造不完整的對象,如下面代碼所示:
public class Escape{ 
    private int thisCanBeEscape = 0;
    public Escape(){
       new InnerClass();
    }
    private classInnerClass { 
        public InnerClass() { 
        //這裏可以在Escape對象完成構造前提前引用到Escape的private變量
          System.out.println(Escape.this.thisCanBeEscape);
       }
    }
    public static void main(String[] args) { 
        newEscape();
    }
}
    上面的內部類的實例包含了對封裝實例隱含的引用,這樣在對象沒有被正確構造之前,就會被髮布,有可能會有不安全因素。 
一個導致this引用在構造期間逸出的錯誤,是在構造函數中啓動一個線程,無論是隱式啓動線程,還是顯式啓動線程,都會造成this引用逸出,新線程總會在所屬對象構造完畢前看到它。所以如果要在構造函數中創建線程,那麼不要啓動它,而應該採用一個專有的start或initialize方法來統一啓動線程。我們可以採用工廠方法和私有構造函數來完成對象創建和監聽器的註冊,這樣就可以避免不正確的創建。記住,我們的目的是,在對象未完成構造之前,不可以將其發佈。 
 
 
安全發佈對象 
   如果不正確的發佈了可變對象,那麼會導致兩種錯誤。首先,發佈線程以外的任何線程都可以看到被髮布對象的過期值;其次更嚴重的情況是,線程看到的被髮布對象的引用是最新的,然而被髮布對象的狀態卻是過期的。如果一個對象是可變對象,那麼它就要被安全發佈,通常發佈線程與消費線程必須同步化。一個正確創建的對象可以通過下列條件安全發佈:
    1、通過靜態初始化器初始化對象引用。 
    2、將發佈對象的引用存儲到volatile域或者具有原子性的域中(如:java5.0中的AtomicReference)。 
    3、將發佈對象引用存放到正確創建的對象的final域中。 
    4、將發佈對象引用存放到由鎖保護的域中(如:同步化的容器)。 
 
    如果要發佈一個被靜態創建的對象,最簡單的方式就是使用靜態初始化器,如下面代碼所示:public static Holder holder=new Holder();靜態初始化器由JVM在類初始化時執行,JVM在執行靜態變量的初始化時會有內在同步保護,因此可以保證對象的安全發佈。 
 
 
高效不可變對象 
    有些對象在發佈後就不會被修改,其他線程要在沒有額外同步的情況下安全的訪問它們,此時安全的發佈就是至關重要的。所有的安全發佈機制都能保證,只要一個對象在發佈當時的狀態對所有訪問線程都可見,那麼到它的引用也都可見。如果發佈時的狀態不會再改變,那麼就必須確保任意訪問是安全的。
    一個對象是可變的,但是它的狀態不會在發佈後被修改,這樣的對象稱作“高效不可變對象”。這種對象沒有滿足我在上一篇文章中所說的不可變對象的條件,但是這些對象在發佈後可以被簡單的當做不可變對象來使用,另外由於減少了同步,使用它們還會提高效率。如下面代碼所示:
    public Map<String,Date> lastlogin=Collections.synchonizedMap(new HashMap<String,Date>());
    Date對象本身是可變的,每當Date被跨線程來訪問都要使用鎖來確保訪問安全。但是此時我們卻可以把它當作一個不可變對象來使用,因爲我們將Date對象置入了一個線程安全的HashMap容器中,此時訪問這些Date對象值就不再需要額外的同步了。因此任何線程都可以在沒有額外同步的情況下安全的使用一個高效不可變對象,但前提是這些對象必須被安全發佈,即必須滿足上面提到的安全發佈條件。 
 
 
安全地共享對象 
    現在我們來總結一下,在併發編程中的一些安全共享對象的策略。
    1、線程限制:一個被線程限制的對象,由線程獨佔,並且只能被佔有它的線程修改。
    2、共享只讀:一個共享只讀的對象,在沒有額外同步的情況下,可以被多個線程併發訪問,但是任何線程都不能修改它。
    3、線程安全對象:一個線程安全的對象或者容器,在內部通過同步機制來保證線程安全,所以其他線程無需額外的同步就可以通過公共接口隨意訪問它。
    4、被守護對象:被守護對象只能通過獲取特定的鎖來訪問
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章