1. 程式人生 > >Java多執行緒中的阻塞佇列和併發集合

Java多執行緒中的阻塞佇列和併發集合

 本章主要探討在多執行緒程式中與集合相關的內容。在多執行緒程式中,如果使用普通集合往往會造成資料錯誤,甚至造成程式崩潰。Java為多執行緒專門提供了特有的執行緒安全的集合類,通過下面的學習,您需要掌握這些集合的特點是什麼,底層實現如何、在何時使用等問題。

3.1 BlockingQueue介面

java阻塞佇列應用於生產者消費者模式、訊息傳遞、並行任務執行和相關併發設計的大多數常見使用上下文。

 BlockingQueueQueue介面基礎上提供了額外的兩種型別的操作,分別是獲取元素時等待佇列變為非空和新增元素時等待空間變為可用。

 BlockingQueue新增操作的四種形式:

Java多執行緒 <wbr>阻塞佇列和併發集合

 插入操作是指向佇列中新增一個元素,至於元素存放的位置與具體佇列的實現有關。移除操作將會移除佇列的頭部元素,並將這個移除的元素作為返回值反饋給呼叫者。檢查操作是指返回佇列的頭元素給呼叫者,佇列不對這個頭元素進行刪除處理。

 丟擲異常形式的操作,在佇列已滿的情況下,呼叫add方法將會丟擲IllegalStateException異常。如果呼叫remove方法時,佇列已經為空,則丟擲一個NoSuchElementException異常。(實際上,remove方法還可以附帶一個引數,用來刪除佇列中的指定元素,如果這個元素不存在,也會丟擲NoSuchElementException異常)。如果呼叫

element檢查頭元素,佇列為空時,將會丟擲NoSuchElementException異常。

 特殊值操作與丟擲異常不同,在出錯的時候,返回一個空指標,而不會丟擲異常。

 阻塞形式的操作,呼叫put方法時,如果佇列已滿,則呼叫執行緒阻塞等待其它執行緒從佇列中取出元素。呼叫take方法時,如果阻塞佇列已經為空,則呼叫執行緒阻塞等待其它執行緒向佇列新增新元素。

 超時形式操作,在阻塞的基礎上新增一個超時限制,如果等待時間超過指定值,丟擲InterruptedException

 阻塞佇列實現了Queue介面,而Queue介面實現了Collection介面,因此BlockingQueue

也提供了remove(e)操作,即從佇列中移除任意指定元素,但是這個操作往往不會按預期那樣高效的執行,所以應當儘量少的使用這種操作。

 阻塞佇列與併發佇列(例如ConcurrentLinkQueue)都是執行緒安全的,但使用的場合不同。

 Graphic3-1給出了阻塞佇列的介面方法,Graphic3-2給出了阻塞佇列的實現類結構。

Graphic 3-1 BlockingQueue介面

Java多執行緒 <wbr>阻塞佇列和併發集合

Graphic3-2阻塞佇列的實現類

Java多執行緒 <wbr>阻塞佇列和併發集合

3.1.1 ArrayBlockingQueue

 一個以陣列為基礎的有界阻塞佇列,此佇列按照先進先出原則對元素進行排序。佇列頭部元素是佇列中存在時間最長的元素,佇列尾部是存在時間最短的元素,新元素將會被插入到佇列尾部。佇列從頭部開始獲取元素。

 ArrayBlockingQueue是“有界快取區”模型的一種實現,一旦建立了這樣的快取區,就不能再改變緩衝區的大小。ArrayBlockingQueue的一個特點是,必須在建立的時候指定佇列的大小。當緩衝區已滿,則需要阻塞新增的插入操作,同理,當緩衝區已空需要阻塞新增的提取操作。

 ArrayBlockingQueue是使用的是迴圈佇列方法實現的,對ArrayBlockingQueue的相關操作的時間複雜度,可以參考迴圈佇列進行分析。

3.1.2 LinkedBlockingQueue

 一種通過連結串列實現的阻塞佇列,支援先進先出。佇列的頭部是佇列中保持時間最長的元素,佇列的尾部是保持時間最短的元素。新元素插入佇列的尾部。可選的容量設定可以有效防止佇列過於擴張造成系統資源的過多消耗,如果不指定佇列容量,佇列預設使用Integer.MAX_VALUELinkedBlockingQueue的特定是,支援無限(理論上)容量。

3.1.3 PriorityBlockingQueue

 PriorityBlockingQueue是一種基於優先順序進行排隊的無界佇列。佇列中的元素按照其自然順序進行排列,或者根據提供的Comparator進行排序,這與構造佇列時,提供的引數有關。

 使用提取方法時,佇列將返回頭部,具有最高優先順序(或最低優先順序,這與排序規則有關)的元素。如果多個元素具有相同的優先順序,則同等優先順序間的元素獲取次序無特殊說明。

 優先順序佇列使用的是一種可擴充套件的陣列結構,一般可以認為這個佇列是無界的。當需要新新增一個元素時,如果此時陣列已經被填滿,優先佇列將會自動擴充當前陣列(一般認為是,先分配一個原陣列一定倍數空間的陣列,之後將原陣列中的元素拷貝到新分配的陣列中,釋放原陣列的空間)。

 如果使用優先順序佇列的iterator變數佇列時,不保證遍歷次序按照優先順序大小進行。因為優先順序佇列使用的是堆結構。如果需要按照次序遍歷需要使用Arrays.sort(pq.toArray())。關於堆結構的相關演算法,請查考資料結構相關的書籍。

 PriorityBlockingQueue的實現過程中聚合了PriorityQueue的一個例項,並且優先佇列的操作完全依賴與PriorityQueue的實現。在PriorityQueue中使用了一個一維陣列來儲存相關的元素資訊。一維陣列使用最小堆演算法進行元素新增。

 Graphic3-3PriorityBlockingQueue的類關係

Java多執行緒 <wbr>阻塞佇列和併發集合

3.1.4 DelayQueue

 一個無界阻塞佇列,只有在延時期滿時才能從中提取元素。如果沒有元素到達延時期,則沒有頭元素。

3.2 併發集合

 在多執行緒程式中使用的集合類,與普通程式中使用的集合類是不同的。因為有可能多個執行緒同時訪問或修改同一集合,如果使用普通集合,很可能造成相應操作出現差錯,甚至崩潰。Java提供了用於執行緒訪問安全的集合。(前面討論的BlockingQueue也是這裡集合中的一種)。下面針對這些集合,以及集合中使用的相應演算法進行探討。在設計演算法時,僅對相應演算法進行簡要說明,如果讀者需要深入瞭解這些演算法的原理,請參考其他的高階資料結構相關的書籍。

3.2.1 ConcurrentMap介面

 ConcurrentMap介面在Map介面的基礎上提供了一種執行緒安全的方法訪問機制。ConcurrentMap介面額外提供了多執行緒使用的四個方法,這四個方法實際是對Map已有方法的一個組合,並對這種組合提供一種原子操作。Graphic3-4給出了ConcurrentMap相關的操作。Graphic3-5給出了ConcurrentMap的實現類關係圖。

 Graphic3-5中可以看出ConcurrentNavigableMap繼承自ConcurrentMapConcurrentNavigableMap是一種SortedMap,就是說,對映中的元素會根據鍵值進行排序的。在java.util類庫中,有兩個類實現了SortedMap介面,分別是TreeMapConcurrentSkipListMapTreeMap使用的是紅黑樹結構。而ConcurrentSkipListMap使用作為底層實現的SkipList(翻譯為跳錶)資料結構。此外ConcurrentHashMap實現了ConcurrentMap介面,使用的是HashMap方法。

Graphic3-4 ConcurrentMap

Java多執行緒 <wbr>阻塞佇列和併發集合

Graphic3-5 實現ConcurrentMap介面。

Java多執行緒 <wbr>阻塞佇列和併發集合

3.2.1.1 TreeMap

 儘管TreeMap不是執行緒安全的,但是基於其資料結構的複雜性和方便對比說明,還是在這裡簡單提一下。TreeMap實現了SortedMap介面。TreeMap使用的是紅黑樹(這是高等資料結構中的一種),在紅黑樹演算法中,當新增或刪除節點時,需要進行旋轉調整樹的高度。使用紅黑樹演算法具有較好的操作特性,插入、刪除、查詢都能在O(log(n))時間內完成。紅黑樹理論和實現是很複雜的,但可以帶來較高的效率,因此在許多場合也得到了廣泛使用。紅黑樹的一個缺陷在於,可變操作很可能影響到整棵樹的結構,針對修改的區域性效果不好。相關演算法請參考

 TreeMap不是執行緒安全的,如果同時有多個執行緒訪問同一個Map,並且其中至少有一個執行緒從結構上修改了該對映,則必須使用外部同步。可以使用方法來包裝該對映。(注意使用包裝器包裝的SortMap是執行緒安全的,但不是併發的,效率上很可能遠遠不及ConcurrentSkipListMap,因此使用包裝器的方法並不十分推薦,有人認為那是一種過時的做法。包裝器使用了鎖機制控制對Map的併發訪問,但是這種加鎖的粒度可能過大,很可能影響併發度)。

3.2.1.2 ConcurrentSkipListMap

 另外一種實現了SortedMap介面的對映表是ConcurrentSkipListMapConcurrentSkipListMap提供了一種執行緒安全的併發訪問的排序對映表。SkipList(跳錶)結構,在理論上能夠在O(log(n))時間內完成查詢、插入、刪除操作。SkipList是一種紅黑樹的替代方案,由於SkipList與紅黑樹相比無論從理論和實現都簡單許多,所以得到了很好的推廣。SkipList是基於一種統計學原理實現的,有可能出現最壞情況,即查詢和更新操作都是O(n)時間複雜度,但從統計學角度分析這種概率極小。Graphic3-6給出了SkipList的資料表示示例。有關skipList更多的說明可以參考:  這裡不在累述。希望讀者自行學習。

 使用SkipList型別的資料結構更容易控制多執行緒對集合訪問的處理,因為連結串列的區域性處理性比較好,當多個執行緒對SkipList進行更新操作(指插入和刪除)時,SkipList具有較好的區域性性,每個單獨的操作,對整體資料結構影響較小。而如果使用紅黑樹,很可能一個更新操作,將會波及整個樹的結構,其區域性性較差。因此使用SkipList更適合實現多個執行緒的併發處理。在非多執行緒的情況下,應當儘量使用TreeMap,因為似乎紅黑樹結構要比SkipList結構執行效率略優(無論是時間複雜度還是空間複雜度,作者沒有做夠測試,只是直覺)。此外對於併發性相對較低的並行程式可以使用Collections.synchronizedSortedMapTreeMap進行包裝,也可以提供較好的效率。對於高併發程式,應當使用ConcurrentSkipListMap,能夠提供更高的併發度。

 所以在多執行緒程式中,如果需要對Map的鍵值進行排序時,請儘量使用ConcurrentSkipListMap,可能得到更好的併發度。

 注意,呼叫ConcurrentSkipListMapsize時,由於多個執行緒可以同時對對映表進行操作,所以對映表需要遍歷整個連結串列才能返回元素個數,這個操作是個O(log(n))的操作。

Graphic3-6 SkipList示例

  Java多執行緒 <wbr>阻塞佇列和併發集合    

3.2.1.3 HashMap

 Map類的另外一個實現是HashMapHashMap使用Hash表資料結構。HashMap假定雜湊函式能夠將元素適當的分佈在各桶之間,提供一種接近O(1)的查詢和更新操作。但是如果需要對集合進行迭代,則與HashMap的容量和桶的大小有關,因此HashMap的迭代效率不會很高(尤其是你為HashMap設定了較大的容量時)。

 HashMap效能有影響的兩個引數是,初始容量和載入因子。容量是雜湊表中桶的數量,初始容量是雜湊表在建立時的容量。載入因子是雜湊表在容器容量被自動擴充之前,HashMap能夠達到多滿的一種程度。當hash表中的條目數超出了載入因子與當前容量的乘積時,Hash表需要進行rehash操作,此時Hash表將會擴充為以前兩倍的桶數,這個擴充過程需要進行完全的拷貝工作,效率並不高,因此應當儘量避免。合理的設定Hash表的初始容量和載入因子會提高Hash表的效能。HashMap自身不是執行緒安全的,可以通過CollectionssynchronizedMap方法對HashMap進行包裝。

3.2.1.4 ConcurrentHashMap

 ConcurrentHashMap類實現了ConcurrentMap介面,並提供了與HashMap相同的規範和功能。實際上Hash表具有很好的區域性可操作性,因為對Hash表的更新操作僅會影響到具體的某個桶(假設更新操作沒有引發rehash),對全域性並沒有顯著影響。因此ConcurrentHashMap可以提供很好的併發處理能力。可以通過concurrencyLevel的設定,來控制併發工作執行緒的數目(預設為16),合理的設定這個值,有時很重要,如果這個值設定的過高,那麼很有可能浪費空間和時間,使用的值過低,又會導致執行緒的爭用,對數量估計的過高或過低往往會帶來明顯的效能影響。最好在建立ConcurrentHashMap時提供一個合理的初始容量,畢竟rehash操作具有較高的代價。

3.2.2 ConcurrentSkipListSet

 實際上SetMap從結構來說是很像的,從底層的演算法原理分析,SetMap應當屬於同源的結構。所以Java也提供了TreeSetConcurrentSkipListSet兩種SortedSet,分別適合於非多執行緒(或低併發多執行緒)和多執行緒程式使用。具體的演算法請參考前述的Map相關介紹,這裡不在累述。

3.2.3 CopyOnWriteArrayList

 CopyOnWriteArrayListArrayList的一個執行緒安全的變體,其中對於所有的可變操作都是通過對底層陣列進行一次新的複製來實現的。

 由於可變操作需要對底層的資料進行一次完全拷貝,因此開銷一般較大,但是當遍歷操作遠遠多於可變操作時,此方法將會更有效,這是一種被稱為“快照”的模式,陣列在迭代器生存期內不會發生更改,因此不會產生衝突。建立迭代器後,迭代器不會反映列表的新增、移除或者更改。不支援在迭代器上進行removesetadd操作。CopyOnWriteArraySetCopyOnWriteArrayList相似,只不過是Set類的一個變體。

3.2.3 Collections提供的執行緒安全的封裝

 Collections中提供了synchronizedCollectionsynchronizedListsynchronizedMapsynchronizedSetsynchronizedSortedMapsynchronizedSortedMap等方法可以完成多種集合的執行緒安全的包裝,如果在併發度不高的情況下,可以考慮使用這些包裝方法,不過由於Concurrent相關的類的出現,已經不這麼提倡使用這些封裝了,這些方法有些人稱他們為過時的執行緒安全機制。

3.2.4 簡單總結

 提供執行緒安全的集合簡單概括分為三類,首先,對於併發性要求很高的需求可以選擇以Concurrent開頭的相應的集合類,這些類主要包括:ConcurrentHashMapConcurrentLinkedQueueConcurrentSkipListMapConcurrentSkipSet。其次對於可變操作次數遠遠小於遍歷的情況,可以使用CopyOnWriteArrayListCopyOnWriteArraySet類。最後,對於併發規模比較小的並行需求可以選擇Collections類中的相應方法對已有集合進行封裝。

 此外,本章還對一些集合類的底層實現進行簡單探討,對底層實現的瞭解有利於對何時使用何種方式作出正確判斷。希望大家能夠將涉及到原理(主要有迴圈佇列、堆、HashMap、紅黑樹、SkipList)進行仔細研究,這樣才能更深入瞭解Java為什麼這樣設計類庫,在什麼情況使用,應當如何使用。

相關推薦

Java執行Synchronized簡介Static Synchronized的區別

在進行Java開發時,多執行緒的開發是經常會使用的。首先會問一個小問題啊,在Java中有幾種方法可以建立一個執行緒? 我給的答案是3種。(如果還有其他的請留言告訴我哈。) 1、建立直接繼承自Thread類建立執行緒子類。   步驟如下:a 定義一個子類,同時

Java執行的finalstatic

看Android的多執行緒發現其實是Java的多執行緒。我找了一本Java程式設計思想學習Java的併發機制。寫了一個demo,遇到一些問題,雖然最後想明白了,但是也暴露了我的Java基礎差勁的事實。之後我會通過寫部落格的方式來提高Java水平。現在說一下我的問

Java執行-BlockingQueue(阻塞佇列)

前言:   BlockingQueue是多執行緒安全的佇列,它有兩種常見的阻塞場景。    佇列中沒有資料的情況下,消費者端的所有執行緒都會被自動阻塞(掛起),直到有資料放入佇列。 當佇列中填滿

iOS執行佇列執行的排列組合結果分析

本文是對以往學習的多執行緒中知識點的一個整理。 多執行緒中的佇列有:序列佇列,併發佇列,全域性佇列,主佇列。 執行的方法有:同步執行和非同步執行。那麼兩兩一組合會有哪些注意事項呢? 如果不是在董鉑然部落格園看到這邊文章請 點選檢視原文 提到多執行緒,也就是四種,pthread,NSthread,GCD

java執行-03-阻塞佇列簡介

宣告 該系列文章只是記錄本人回顧java多執行緒程式設計時候記錄的筆記。文中所用語言並非嚴謹的專業術語(太嚴謹的術語其實本人也不會……)。難免有理解偏差的地方,歡迎指正。 另外,大神請繞路。不喜勿噴。 畢竟好記性不如爛筆頭嘛,而且許多東西只要不是

Java執行,JoinInterrupt()方法的使用

更多詳細的解答請轉至:http://my.oschina.net/summerpxy/blog/198457;http://uule.iteye.com/blog/1101994;(比如有一個執行緒t.當在Main執行緒中呼叫t.join()的時候,那麼Main執行緒必須拿

Java執行____BlockingQueue阻塞佇列使用

package com.frame.base.thread; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; /** * 併發程式設計

Java執行阻塞佇列併發集合

 本章主要探討在多執行緒程式中與集合相關的內容。在多執行緒程式中,如果使用普通集合往往會造成資料錯誤,甚至造成程式崩潰。Java為多執行緒專門提供了特有的執行緒安全的集合類,通過下面的學習,您需要掌握這些集合的特點是什麼,底層實現如何、在何時使用等問題。 3.1 Blo

Java執行 阻塞佇列併發集合

本章主要探討在多執行緒程式中與集合相關的內容。在多執行緒程式中,如果使用普通集合往往會造成資料錯誤,甚至造成程式崩潰。Java為多執行緒專門提供了特有的執行緒安全的集合類,通過下面的學習,您需要掌握這些集合的特點是什麼,底層實現如何、在何時使用等問題。 3.1Blockin

Java執行阻塞佇列

Java語言提供了java.util.concurrent包解決執行緒同步問題,concurrent包中的阻塞佇列BlockingQueue能夠很好地執行緒同步問題, 介面BlockingQueue提供如下幾個執行緒同步方法 儲存資料:   offer(obj):向佇列BlockingQue

Java執行start()run()的區別

Java的執行緒是通過java.lang.Thread類來實現的。VM啟動時會有一個由主方法所定義的執行緒。可以通過建立Thread的例項來建立新的執行緒。每個執行緒都是通過某個特定Thread物件所對應的方法run()來完成其操作的,方法run()稱為執行緒體。通過呼叫Thread類的start(

java執行的sleep()、wait()、notify()物件鎖的關係

1、sleep()不釋放物件鎖。 2、wait()釋放物件鎖。 3、notify()不釋放物件鎖。 (1)、notify釋放鎖嗎?不要誤導別人。notifty()只是喚醒此物件監視器上等待的單個執行緒,直到當前執行緒釋放此物件上的鎖,才有可能繼續執行被喚醒的執行緒。 (2)

Java執行避免在生產者消費者場景出現假死

在多執行緒程式設計中,如果所有執行緒全部都經由wait()方法進入等待狀態,那麼程式就進入了假死狀態 程式示例 考慮這個例子,來自《Java多執行緒程式設計核心技術》: 生產者類P: //生產者 public class P { private Stri

Java執行 synchronizedLock的區別

我們已經瞭解了Java多執行緒程式設計中常用的關鍵字synchronized,以及與之相關的物件鎖機制。這一節中,讓我們一起來認識JDK 5中新引入的併發框架中的鎖機制。 我想很多購買了《Java程式設計師面試寶典》之類圖書的朋友一定對下面這個面試題感到非常熟悉: 問:請對比synchronized與java

java執行Thread類Runnable介面使用方法

java提供了兩種執行緒方式,一種是繼承java.lang包下的Thread類,覆寫Thread類的run()方法,在run()方法中實現執行線上程上的程式碼!第二種是實現Runnable介面建立多執行

Java執行的虛假喚醒如何避免

## 先來看一個例子 一個賣面的麵館,有一個做面的廚師和一個吃麵的食客,需要保證,廚師做一碗麵,食客吃一碗麵,不能一次性多做幾碗面,更不能沒有面的時候吃麵;按照上述操作,進行十輪做面吃麵的操作。 ## 用程式碼說話 首先我們需要有一個資源類,裡面包含面的數量,做面操作,吃麵操作; 當面的

執行佇列不一定需要執行安全

兩個執行緒,主執行緒中update update(){   while(queue.count >0){     //process....     queue.pop()   } }   子執行緒中: queue.enqueue(data)   這樣做是沒有問

java執行的異常處理

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

java執行顯式鎖的輪詢檢測策略

顯式鎖簡介 java5.0之前,在協調對共享物件的訪問時可以使用的機制只有synchronized和volatile,java5.0增加了一種新的機制:ReentrantLock。 鎖像synchronized同步塊一樣,是一種執行緒同步機制,與synchronized不同的是ReentrantLock提

JAVA執行 重入鎖讀寫鎖

在java多執行緒中,我們真的可以使用synchronized關鍵字來實現執行緒間的同步互斥工作,那麼其實還有一個更優秀的機制去完成這個“同步互斥”工作,他就是Lock物件,重入鎖和讀寫鎖。他們具有比synchronized更為強大的功能,並且有嗅探鎖定、多路分支等功能。 一、重入鎖