1. 程式人生 > >執行緒安全物件簡介

執行緒安全物件簡介

基本的執行緒安全物件有Vector,HashTable,還有經過Collections.synchronizedCollection()方法包裝的集合物件。Java併發包中提供的安全型別有ConcurentHashMap,ConcurentLinkedQueue,CopyOnWriteArrayList,還有BlockingQueue的實現型別。另外還有Java原子包中提供的AtomicInteger,AtomicLong等原子型別。

經過Collections.synchronizedMap()包裝的HashMap是一個執行緒安全物件,如Map m = Collections. synchronizedMap(new HashMap())。Collections.synchronizedMap()會生成一個名為SynchronizedMap的Map,它使用委託將自己所有Map相關的功能交給傳入的HashMap實現,而自己主要負責保證執行緒安全。實際上就是將HashMap相關的操作方法加上了synchronized關鍵字以保證執行緒安全,在併發級別不高時可以使用。

ConcurentHashMap

ConcurentHashMap是Java併發包提供的併發HashMap,它專門為併發進行了效能優化,更加適合多執行緒的場合。其內部實現原理是:它實現了一個Segement陣列,對於每一個數組元素來講,都相當於一個HashTable。在同一個segement上操作時,會加鎖進行互斥同步。而在不同的segement上操作時則是無鎖的。它引入了“多段鎖”的概念,將鎖的粒度從整個物件上細化到了具體每個segement陣列的元素上,以此達到提升併發效能的目的。

ConcurentLinkedQueue

高併發佇列使用連結串列作為其資料結構。ConcurentLinkedQueue是高併發環境下效能最好的佇列。其佇列節點用Node表示,對Node進行操作時使用了CAS操作。因為沒有使用鎖而是單純使用CAS操作來保證執行緒安全,因此大大增加了設計和實現的難度,但是帶來的好處是效能得到飛速提升。

CopyOnWriteArrayList

在很多應用場景中,讀操作可能遠遠大於寫操作,對於這種場景,希望看到的是讀操作儘可能快,寫即使慢一些也沒有太大關係。

由於讀操作根本不會修改原有的資料,因此對於每次讀取都進行加鎖是一種資源浪費。根據讀寫鎖的思想,讀鎖和寫鎖之間確實也不衝突,但是讀操作會受到寫操作的阻礙,當寫發生時,讀就必須等待,否則可能讀到不一致的資料。同理,讀操作在進行時候寫操作也必須等待。

為了將效能發揮到極致,併發包中提供了CopyOnWriteArrayList類,對它來說,讀取完全不用加鎖,而且寫入也不會阻塞讀取操作,只有寫入和寫入之間需要進行同步等待。這樣一來,讀操作的效能就會大幅度提升。其實現是:在寫入操作的時候進行一次自我複製,將修改的內容寫入副本中,寫完之後再將修改完的副本替換原來的資料,這樣就可以保證寫操作不會影響讀了。

BlockingQueue

假設有兩個執行緒A和B,在某種模式下兩個執行緒間需要進行通訊,即A執行緒能夠通知B,但是又希望A不知道B的存在。這樣將來重構或者升級時完全可以不修改A而直接把執行緒B升級為執行緒C,保證系統的平滑過渡。這種情形下就可以使用BlockingQueue來實現訊息通訊。BlockingQueue是一個介面,而不是一個具體的實現。其主要的實現類有ArrayBlockQueue和LinkedBlockigQueue。前者基於陣列實現,後者基於連結串列實現。也正為如此,ArrayBlockingQueue更適合做有界佇列,因為佇列中可容納的最大元素需要在佇列建立時指定。而LinkedBlockingQueue適合做無界佇列,或者那些邊界值很大的佇列,因為其內部元素可以動態增加,也不會因為初值容量很大而一下子消耗掉大量記憶體空間。

BlockingQueue之所以適合作為資料共享的通道,其關鍵還在於Blocking上。Blocking是阻塞的意思,它讓服務執行緒在佇列為空時進行等待,當有新的訊息進入佇列後自動將執行緒喚醒。其實現阻塞的關鍵是:存入元素的put()方法和取元素的take()方法。put()方法將元素壓入佇列末尾,當佇列滿時就阻塞等待,知道佇列中有空閒的位置。take()方法從佇列中取出元素,當佇列為空時就等待,知道佇列中有可用元素。BlockingQueue使用非常普通,在生產者和消費者模型中,可以將其作為緩衝區來使用。

SkipList

在JDK的併發包中除了常用的雜湊表外還實現了一種有趣的資料結構,那就是跳錶。跳錶是一種可以用來快速查詢的資料結構,有點類似於平衡樹。但有一個重要的區別,對平衡樹的插入和刪除往往可能導致平衡樹進行一次全域性的調整。對跳錶的插入和刪除只需要對整個資料結構的區域性進行操作即可。這樣帶來的好處是,在高併發情況下,平衡樹需要一個全域性的鎖來保證執行緒安全,而跳錶值只需要區域性鎖即可,併發效能更好。

跳錶使用多層連結串列實現,最底層的連結串列維護了跳錶內所有的元素,每上面一層都是下面一層的子集。跳錶內所有連結串列的元素都是排序的,查詢時可以從頂級連結串列開始找,查詢效率是O(logN)。

可以使用跳錶實現Map,如ConcurentSkipListMap。用跳錶實現的Map裡所有的元素都是排序的,對跳錶進行遍歷時會得到一個有序的結果。跳錶的節點用Node實現,對Node的所有操作均使用CAS方法。

原子型別

原子型別為Java的atomic包中提供的執行緒安全類,主要有AtomicInteger,AtomicLong,AtomicBoolean和AtomicReference等。這些型別的實現裡面用到了CAS操作,通過非阻塞同步的方式實現了執行緒安全。具體實現可以去閱讀原始碼。在jdk1.8中有個型別LongAdder可以替換AtomicInteger,因為與AtomicInteger相比,在併發量小時兩者效能接近,併發量大時效率大大提高。