Java併發編程基礎構建模塊(03)——阻塞隊列

        容器中還有一種數據結構十分有用,就是隊列,實現了FIFO(公平性)或者LIFO(處理最近發生的事)的操作,解決了很多數據傳輸,任務分配等方面問題。多線程環境下,如何更“高效、安全”是最主要的問題,好在JDK提供了BlockingQueue,阻塞隊列,極大的方便了我們的操作。

        隊列最主要的3個方法:插入(插入一個元素)、移除(獲得並刪除)、檢查(獲得不刪除)這3個操作。普通隊列爲每種方法提供了2種實現,當操作失敗時(比如沒有元素,卻執行了刪除;隊列滿了,再添加元素等),一種會拋出異常,另一種會返回false:

        

        而阻塞隊列額外新增了2種實現方式,一種是阻塞,掛起當前線程,直到可以操作爲止;另一種是超時,其實就是阻塞和返回特殊只的合體,在一定時間內阻塞,時間內可以操作則操作,超過指定時間扔不能操作,則返回特殊值。

        

        多線程情況下,使用阻塞隊列極容易實現數據共享,如非常經典的生產者和消費者模式。生產者和消費者沒有任何耦合,生產者只需要往隊列放入數據,消費者只需要從隊列獲取數據然後處理,雙方相互不依賴,當沒有數據可以消費時,消費者阻塞,等待一旦有數據馬上處理,生產者也是這樣,當隊列滿了,阻塞等待可以放入數據爲止。

        比如使用生產者消費者模式,做一個數據傳輸功能,數據庫中被修改過的記錄需要傳輸到其他系統,涉及的還有附件等信息,所以傳輸1條大約10秒,由於很多種情況修改數據,導致無法做到修改後馬上傳輸,只能定時掃描哪些數據需要傳輸。示例如下:

        數據掃描類(生產者):

        

        數據傳輸類(消費者):

        

        定時任務調用者:

        

        其實這個示例也能看出來,生產者和消費者的數量是需要根據實際情況調整的,如果生產速率超過了消費速率,則工作就會積累,消耗額外內存(這也是爲什麼上面對象設置了1000大小),如果消費速率超過生產速率,則資源得不到有效利用。

        上面我們用到了ArrayBlockingQueue,其實接口BlockingQueue主要有5個實現類:

        1、 ArrayBlockingQueue:基於數組實現的阻塞隊列,必須初始化一個大小才能使用,超過大小再放入元素則阻塞或失敗。默認情況不是按照FIFO這種模式的,可以設置成嚴格按照FIFO這種公平的模式,不過公平性會降低吞吐量。寫入數據和獲取數據使用了一個鎖,所以兩者不能並行運行。(摘抄:DougLea之所以沒這樣去做,也許是因爲ArrayBlockingQueue的數據寫入和獲取操作已經足夠輕巧,以至於引入獨立的鎖機制,除了給代碼帶來額外的複雜性外,其在性能上完全佔不到任何便宜。)

        2、 LinkedBlockingDeque:基於鏈表實現的阻塞隊列,可以設置大小,如果不設置,默認大小爲Integer.MAX_VALUE,所以要小心生產速率超過消費速率時,有可能造成內存溢出。寫入數據和獲取數據使用2個鎖,所以兩者可以完全可以並行運行。還有一點比較重要,LinkedBlockingDeque可以雙向操作,是一個阻塞雙端隊列。

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

        3、 DelayQueue:延遲阻塞隊列,放入隊列中的元素只有過了設定的時間才能取出,是一個沒有大小限制的隊列,因此往隊列中插入數據的操作(生產者)永遠不會被阻塞,而只有獲取數據的操作(消費者)纔會被阻塞。使用較少,一旦使用卻非常好用,比如用於處理緩存對象,超過多久還沒被用到的緩存對象就清除,或者處理超時未響應的連接隊列等。

        4、 PriorityBlockingQueue:基於自然順序的阻塞隊列,也就是說可以通過Comparable進行排序,沒有大小限制,也就是並不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者,因此使用的時候要特別注意,生產者生產數據的速度絕對不能快於消費者消費數據的速度,否則時間一長,會最終耗盡所有的可用堆內存空間。

        5、 SynchronousQueue:沒有緩衝的阻塞隊列,相當於大小永遠爲0(其實沒有容量),無法進行獲取但不刪除(如peek)等操作。相比有緩衝的阻塞隊列,吞吐量有所降低(有緩衝的生產消費可以一對多,多對一,除非生產速率和消費速率完全相同,相比較吞吐量纔會一樣),但是響應時間是最快的(沒有緩衝了嘛)。SynchronousQueue支持公平鎖,默認非公平。非公平鎖情況下,如果生產速率和消費速率不一致,再是LIFO隊列,則容易造成某些生產者和消費者永遠得不到處理,此處需要小心

        作爲併發編程基礎構建塊的容器,BlockingQueue不單具備完整隊列所有的基本功能,同時在多線程環境下,自動管理了多線間的自動等待與喚醒等功能,使得開發人員可以忽略這些底層細節,關注更高級的功能。與BlockingQueue類似的,還有BlockingDeque(雙端隊列),BlockingDeque實現了雙端隊列,能夠在隊列頭尾進行高效的插入和刪除操作。

        工作密取適合於既是消費者也是生產者的情況,正如阻塞隊列適用於生產者消費者模式一樣,雙端隊列適用於工作密取,在生產者消費者中,消費者就是消費者,所有消費者共享一個工作隊列,而工作密取中,每個消費者都有各自的雙端隊列(一端生產,一端消費)。當一個工作線程處理數據時(此時是消費者),發現了更多的任務(變身成生產者,生產了更多的任務),然後將任務放到自己消費隊列中(工作共享模式中是別人的),當自己的雙端都處理完時(自己的雙端隊列爲空),再幫助其他線程處理(從其他線程尾部處理,避免了隊列競爭),這樣每個線程都處於忙碌狀態,效率非常高。如網絡爬蟲,處理了一個網頁時,發現裏面更多的鏈接,更多的頁面需要處理。

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