天天看點

阻塞隊列BlockingQueue用法(轉)

多線程環境中,通過隊列可以很容易實作資料共享,比如經典的“生産者”和“消費者”模型中,通過隊列可以很便利地實作兩者之間的資料共享。

假設我們有若幹生産者線程,另外又有若幹個消費者線程。如果生産者線程需要把準備好的資料共享給消費者線程,利用隊列的方式來傳遞資料,就可以很友善地解決他們之間的資料共享問題。但如果生産者和消費者在某個時間段内,萬一發生資料處理速度不比對的情況呢?理想情況下,如果生産者産出資料的速度大于消費者消費的速度,并且當生産出來的資料累積到一定程度的時候,那麼生産者必須暫停等待一下(阻塞生産者線程),以便等待消費者線程把累積的資料處理完畢,反之亦然。

然而,在concurrent包釋出以前,在多線程環境下,我們每個程式員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程式帶來不小的複雜度。好在此時,強大的concurrent包橫空出世了,而他也給我們帶來了強大的BlockingQueue。(在多線程領域:所謂阻塞,在某些情況下會挂起線程(即阻塞),一旦條件滿足,被挂起的線程又會自動被喚醒)

BlockingQueue的兩個常見阻塞場景:

1、當隊列中沒有資料的情況下,消費者端的所有線程都會被自動阻塞(挂起),直到有資料放入隊列。

2、當隊列中填滿資料的情況下,生産者端的所有線程都會被自動阻塞(挂起),直到隊列中有空閑的位置,線程被自動喚醒。

這也是我們在多線程環境下,為什麼需要BlockingQueue的原因。作為BlockingQueue的使用者,我們再也不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因為這一切BlockingQueue都給你一手包辦了。

BlockingQueue成員詳細介紹

1. ArrayBlockingQueue

基于數組的阻塞隊列實作,在ArrayBlockingQueue内部,維護了一個定長數組,以便緩存隊列中的資料對象,這是一個常用的阻塞隊列,除了一個定長數組外,ArrayBlockingQueue内部還儲存着兩個整形變量,分别辨別着隊列的頭部和尾部在數組中的位置。

ArrayBlockingQueue在生産者放入資料和消費者擷取資料,都是共用同一個鎖對象,由此也意味着兩者無法真正并行運作,這點尤其不同于LinkedBlockingQueue;按照實作原理來分析,ArrayBlockingQueue完全可以采用分離鎖,進而實作生産者和消費者操作的完全并行運作。Doug Lea之是以沒這樣去做,也許是因為ArrayBlockingQueue的資料寫入和擷取操作已經足夠輕巧,以至于引入獨立的鎖機制,除了給代碼帶來額外的複雜性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue間還有一個明顯的不同之處在于,前者在插入或删除元素時不會産生或銷毀任何額外的對象執行個體,而後者則會生成一個額外的Node對象。這在長時間内需要高效并發地處理大批量資料的系統中,其對于GC的影響還是存在一定的差別。而在建立ArrayBlockingQueue時,我們還可以控制對象的内部鎖是否采用公平鎖,預設采用非公平鎖。

2. LinkedBlockingQueue

基于連結清單的阻塞隊列,同ArrayListBlockingQueue類似,其内部也維持着一個資料緩沖隊列(該隊列由一個連結清單構成),當生産者往隊列中放入一個資料時,隊列會從生産者手中擷取資料,并緩存在隊列内部,而生産者立即傳回;隻有當隊列緩沖區達到最大值緩存容量時(LinkedBlockingQueue可以通過構造函數指定該值),才會阻塞生産者隊列,直到消費者從隊列中消費掉一份資料,生産者線程會被喚醒,反之對于消費者這端的處理也基于同樣的原理。而LinkedBlockingQueue之是以能夠高效的處理并發資料,還因為其對于生産者端和消費者端分别采用了獨立的鎖來控制資料同步,這也意味着在高并發的情況下生産者和消費者可以并行地操作隊列中的資料,以此來提高整個隊列的并發性能。

作為開發者,我們需要注意的是,如果構造一個LinkedBlockingQueue對象,而沒有指定其容量大小,LinkedBlockingQueue會預設一個類似無限大小的容量(Integer.MAX_VALUE),這樣的話,如果生産者的速度一旦大于消費者的速度,也許還沒有等到隊列滿阻塞産生,系統記憶體就有可能已被消耗殆盡了。

ArrayBlockingQueue和LinkedBlockingQueue是兩個最普通也是最常用的阻塞隊列,一般情況下,在處理多線程間的生産者消費者問題,使用這兩個類足以。

ArrayBlockingQueue和LinkedBlockingQueue的差別

1.隊列中鎖的實作不同

ArrayBlockingQueue實作的隊列中的鎖是沒有分離的,即生産和消費用的是同一個鎖;

LinkedBlockingQueue實作的隊列中的鎖是分離的,即生産用的是putLock,消費用的是takeLock

2.在生産或消費時操作不同

ArrayBlockingQueue實作的隊列中在生産和消費的時候,是直接将枚舉對象插入或移除的;

LinkedBlockingQueue實作的隊列中在生産和消費的時候,需要把枚舉對象轉換為Node<E>進行插入或移除,會影響性能

3.隊列大小初始化方式不同

ArrayBlockingQueue實作的隊列中必須指定隊列的大小;

LinkedBlockingQueue實作的隊列中可以不指定隊列的大小,但是預設是Integer.MAX_VALUE

注意:

1. 在使用LinkedBlockingQueue時,若用預設大小且當生産速度大于消費速度時候,有可能會記憶體溢出

2. 在使用ArrayBlockingQueue和LinkedBlockingQueue分别對10,00000個簡單字元做入隊操作時,LinkedBlockingQueue的消耗是ArrayBlockingQueue消耗的10倍左右,即LinkedBlockingQueue消耗在1500毫秒左右,而ArrayBlockingQueue隻需150毫秒左右。

http://www.cnblogs.com/linjiqin/p/5130559.html

繼續閱讀