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

一、不變性編程

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

  當知足如下條件時,對象纔是不可變的:(1)、對象建立之後其狀態就不能改變;(2)、對象的全部域都是final類型;(3)、對象是正確創造的(在對象建立期間,this引用沒有溢出)。多線程

1.1 final域ide

  關鍵字final用於構造不可變對象,final類型的域是不能修改的(可是final域所引用的對象是可變的,那麼這些引用的對象是能夠修改的),即便對象是可變的,經過將可變對象的某些域聲明爲final類型,至關於告訴維護人員這些域是不可變化的。函數

 

二、正確發佈一個對象this

  正確發佈一個對象遇到的兩個問題:(1)引用自己要被其餘線程看到;(2)對象的狀態要被其餘線程看到。spa

  在多線程編程中,首要的原則,就是要避免對象的共享,由於若是沒有對象的共享,那麼多線程編寫要輕鬆得多,可是,若是要共享對象,那麼除了可以正確的將構造函數書寫正確外,如何正確的發佈也是一個很重要的問題。線程

  咱們看下面的代碼:code

 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內部存在同步機制,因此這種方式初始化對象均可以被安全的發佈。對於可變對象,安全的發佈之時確保在發佈當時狀態的可見性,而在隨後的每次對象的訪問時,一樣須要使用同步來確保修改操做的可見性。

相關文章
相關標籤/搜索