多線程編程之阻塞隊列概述

前言

JDK1.5中引入的接口java.util.concurrent.BlockingQueue定義了一種線程安全的隊列-阻塞隊列。BlockingQueue常用的實現類包括ArrayBlockingQueue,LinkedBlockingQueue和SynchronousQueues,下面來和大家一起學習下這三種阻塞隊列。

阻塞隊列描述

阻塞隊列按照其存儲空間的容量是否受限制來劃分,可以分爲有界隊列和無界隊列,有界隊列的存儲容量限制是由應用存儲指定的,無界隊列的最大存儲容量爲Integer.MAX_VALUE(2^{31}-1)個元素,往隊列中存入一個元素的操作被稱爲put操作,從隊列中取出一個元素的操作被稱爲take操作。put操作相當於生產線程將對象安全發佈到消費者線程,生產者線程執行的put操作前所執行的任何內存操作,對於後續執行take操作的消費者線程而言都是可見的、有序的。

當消費者線程的處理能力低於生產者的處理能力時,產品的生產速率大於消費速率,這會導致隊列中產品積壓,即隊列中的產品越來越多,由此導致隊列中的這些對象所佔用的內存空間以及其他資源越來越多,因此我們需要限制傳輸通道存儲容量,此時,我們可以使用有界阻塞隊列來作爲傳輸通道。

使用有界隊列作爲傳輸通道的另一個好處時可以造成"反壓"的效果,當消費者的處理能力跟不上生產者的處理能力時,隊列中的產品會逐漸積壓到隊列滿,此時生產者會被暫停掉,直到消費者消費了部分產品而使隊列非滿,這相當於生產者暫停其產品生產而給消費者一個跟上其步伐的機會,當然,這裏的代價就是可能會增加上下文切換。

有界隊列可以使用ArrayBlockingQueue或者LinkedBlockingQueue來實現,ArrayBlockingQueue內部使用一個數組作爲其存儲空間,而數組的存儲空間是預先分配的,因爲ArrayBlockingQueue的put操作和take操作本身不會增加垃圾回收的負擔,ArrayBlockingQueue的缺點是其內部在實現put,take操作的時候使用的同一個鎖,從而可能導致鎖的高爭用,進而導致較多的上下文切換。

LinkedBlockingQueue既能實現無界隊列,也能實現有界隊列。LinkedBlockingQueue的其中一個構造器允許我們創建隊列的時候指定隊列容量。LinkedBlockingQueue的優點是其在內部實現put,take操作的時候分別使用了兩個顯示鎖,降低了鎖爭用的可能性。LinkedBlockingQueue的內部存儲空間是一個鏈表,而鏈表節點所需的存儲空間是動態分配的,put操作,take操作都會導致鏈表節點的動態創建和移除,因此LinkedBlockingQueue的缺點是它可能增加了垃圾回收的負擔,另外LinkedBlockingQueue的put,take操作使用的是兩個鎖,因此LinkedBlockingQueue維護其隊列長度(size)時無法使用一個普通的int變量而是使用一個原子變量。這個原子變量可能會被生產者線程和消費者線程爭用。因此它可能導致額外的開銷。

SynchronnousQueue可以被看作是一中特殊的有界隊列,SynchronnousQueue內部並不維護用於存儲隊列元素的存儲空間。設synchronnousQueue爲一個任意的SynchronnousQueue實例,生產線程執行synchronnousQueue.put(E)時如果沒有消費者線程執行synchronnousQueue.take(),那麼該生產者線程會被暫停,直到消費者線程執行了synchronnousQueue.take();類似地,消費者線程執行synchronnousQueue.take()時如果沒有生產者線程synchronnousQueue.put(E),那麼消費者線程會被暫停,直到有生產者線程執行了synchronnousQueue.put(E)。因此,在使用SynchronnousQueue作爲傳輸通道的生產者-消費者模式中,生產者生產一個產品之後,會等待消費者線程來取走這個產品才繼續生產下一個產品,而不像使用ArrayBlockingQueue或者LinkedBlockingQueue作爲傳遞通道的情況下生產者線程將生產好的產品存入隊列就繼續生產下一個產品。從這個點來看,ArrayBlockingQueue和LinkedBlockingQueue更像是一個信箱;郵遞員只需要將普通郵件投遞到指定信箱即可。而不必關心收件人何時會取走郵件;而SynchronnousQueue所實現的通道更像是郵遞員投送掛號信與收件員接觸的情形-郵遞員必須在收件人簽收之後才能離開。因此SynchronnousQueue適合於消費者處理能裏和生產者處理能力相差不大的情況下使用。否則由於生產者線程執行put操作時沒有消費者線程執行take操作,或者消費者線程執行take操作的時候沒有生產者線程執行put的操縱概率比較大,從而導致較多的等待(這意味着上下文的切換)。

隊列可以被看作時生產者和消費者之間的共享資源,因此資源調度的公平性在隊列上也有所體現。佔用隊列的線程可以對隊列進行put或take操作,那麼對隊列的調度就是決定哪個線程可以進行put或者take操作的過程。ArrayBlockingQueue和LinkedBlockingQueue都支持非公平調度也支持公平調度,而LinkedBlockingQueue僅支持非公平調度。

如果生產者線程和消費者線程之前的併發程度較大,那麼這些線程對傳輸通道內部所使用的鎖爭用可能性也會隨之增大。這時,有界隊列的實現適合採用LinkedBlockingQueue,否則我們可以考慮採用ArrayBlockingQueue。

總結

LinkedBlockingQueue適合在生產者線程和消費者線程之間的併發程度較大的情況下使用,ArrayBlockingQueue適合生產者線程和消費者線程之間的併發程度較低的情況下使用,SynchronnousQueue適合在消費者處理能力和生產者處理能力相差不大的情況下使用。

文章來源

摘自-《java多線程編程實戰指南》

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章