1. 程式人生 > >Java多線程——不變性與安全發布

Java多線程——不變性與安全發布

有一種 同時 div 通過 共享 tab block blog hold

1、不變性

  某個對象在被創建後其狀態就不能被修改,那麽這個對象就稱為不可變對象,不可變對象一定是線程安全的。不可變對象很簡單。他們只有一種狀態,並且該狀態由構造函數來控制。

  當滿足以下條件時,對象才是不可變的:(1)、對象創建以後其狀態就不能改變;(2)、對象的所有域都是final類型;(3)、對象是正確創造的(在對象創建期間,this引用沒有溢出)。

1.1 final域

  關鍵字final用於構造不可變對象,final類型的域是不能修改的(但是final域所引用的對象是可變的,那麽這些引用的對象是可以修改的),即使對象是可變的,通過將可變對象的某些域聲明為final類型,相當於告訴維護人員這些域是不可變化的。

2、正確發布一個對象

  正確發布一個對象遇到的兩個問題:(1)引用本身要被其他線程看到;(2)對象的狀態要被其他線程看到。

  在多線程編程中,首要的原則,就是要避免對象的共享,因為如果沒有對象的共享,那麽多線程編寫要輕松得多,但是,如果要共享對象,那麽除了能夠正確的將構造函數書寫正確外,如何正確的發布也是一個很重要的問題。

  我們看下面的代碼:

技術分享
 1 public class Client {
 2     public Holder holder;
 3     
 4     public void initialize(){
 5         holder = new
Holder(42); 6 } 7 } 8 9 10 public class Holder { 11 int n; 12 public Holder(int n) { 13 this.n = n; 14 } 15 public void assertSanity() { 16 if(n != n) 17 throw new AssertionError("This statement is false."); 18 } 19 }
View Code

  在Client類中,Holder對象被發布了,但是這是一個不正確的發布。由於可見性問題,其他線程看到的Holder對象將處於不一致的狀態,即使在該對象的構成構函數中已經正確的該構建了不變性條件,這種不正確的發布導致其他線程看到尚未創建完成的對象。主要是Holder對象的創建不是原子性的,可能還未構造完成,其他線程就開始調用Holder對象。

由於沒有使用同步的方法來卻確保Holder對象(包含引用和對象狀態都沒有)對其他線程可見,因此將Holder成為未正確發布。問題不在於Holder本身,而是其沒有正確的發布。上面沒有正確發布的可能導致的問題:

  • 別的線程對於holder字段,可能會看到過時的值,這樣就會導致空引用,或者是過時的值(即使holder已經被設置了)(引用本身沒有被別的線程看到)
  • 更可怕的是,對於已經更新holder,及時能夠看到引用的更新,但是對於對象的狀態,看到的卻可能是舊值,對於上面的代碼,可能會拋出AssertionError異常

主要是holder = new Holder(42);這個代碼不是原子性的,可能在構造未完成時,其他線程就會調用holder對象引用,從而導致不可預測的結果。

2.1安全發布常用模式

  要安全的發布一個對象,對象的引用和對象的狀態必須同時對其他線程可見。一般一個正確構造的對象(構造函數不發生this逃逸),可以通過如下方式來正確發布:

  (1)、在靜態初始化函數中初始化一個對象引用

  (2)、將一個對象引用保存在volatile類型的域或者是AtomicReference對象中

  (3)、將對象的引用保存到某個正確構造對象的final類型的域中。

  (4)、將對象的引用保存到一個由鎖保護的域。

  

  在線程安全容器內部同步意味著,在將對象放到某個容器中,比如Vector中,將滿足上面的最後一條需求。如果線程A將對象X放到一個線程安全的容器中,隨後線程B讀取這個對象,那麽可以確保可以確保B看到A設置的X狀態,即便是這段讀/寫X的應用程序代碼沒有包含顯示的同步。下面容器內提供了安全發布的保證:

  (1)、通過將一個鍵或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全將它發布給任何從這些容器中訪問它的線程。

  (2)、通過將某個元素放到Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchroizedList,可以將該元素安全的發布到任何從這些容器中訪問該元素的線程。

  (3)、通過將元素放到BlockingQueue或者是ConcrrentLinkedQueue中,可以將該元素安全的發布到任何從這些訪問隊列中訪問該元素的線程。

  通常,要發布一個靜態構造的對象,最簡單和最安全的方式是使用靜態初始化器: public static Holder = new Holder(42);

  靜態初始化器由JVM在類的初始化階段執行,由於JVM內部存在同步機制,所以這種方式初始化對象都可以被安全的發布。對於可變對象,安全的發布之時確保在發布當時狀態的可見性,而在隨後的每次對象的訪問時,同樣需要使用同步來確保修改操作的可見性。

  

Java多線程——不變性與安全發布