Java(8-5)阻塞隊列與線程安全集合

上一次,我們討論了java多線程的一些細節問題,包括死鎖,讀寫鎖等等。這一節,我們要介紹關於阻塞隊列以及如何使用並且操作線程安全的集合 。

Part 1 阻塞隊列

對於許多線程問題,可以通過一個或多個隊列以優雅且安全的方式將其形式化。

想一下,生產者線程向隊列插入元素,消費者線程取出他們。使用隊列可以安全地從一個線程向另一個線程傳遞數據。例如,我們從銀行轉賬,轉賬線程將轉賬轉賬指令對象(命令模式?) 插入到一個隊列中,而不是直接訪問銀行對象。另一個線程從隊列中取出指令執行轉賬。只有該線程可以訪問該銀行系統的內部。因此不需要同步(當然,線程安全的隊列類的不能不考慮鎖和條件,但是,那是他們的問題。)

當試圖向隊列中添加元素而隊列已滿,或是想從隊列中移出元素而隊列爲空的時候,阻塞隊列導致線程阻塞。

在協調多個線程之間的合作時,阻塞隊列是一個有用的工具。 工作者線程可以週期性地將中間結果存儲在阻塞隊列中。其他的工作者線程移出中間結果並進一步加以修改。隊列會自動地平衡負載。

阻塞隊列具有如下方法:
add 添加一個元素 如果隊列慢,就拋出IllegalStateException異常

element 返回隊列的頭元素 如果隊列空,拋出NoSuchElementException異常

offer 添加一個元素並返回true 如果隊列滿,則返回false

peek 返回隊列的頭元素 如果隊列空則返回null

poll 移出並返回隊列的頭元素 如果隊列空,則返回null

put 添加一個元素 如果隊列滿,則阻塞

remove 移出並返回頭元素 如果隊列空,則拋出NoSuchElementException

take 移出並返回頭元素 如果隊列空,則阻塞

從方法中我們可以發現,設計者是希望我們將阻塞隊列的方法分爲如下:
1.如果將隊列當成線程管理工具來使用,將要用到put和take方法;
2.當試圖向一個滿的隊列添加或者從空的隊列中移出元素的時候,add,remove,element方法操作會拋出異常,但是在多線程中,隊列可能在任何時候變空,或者變滿,因此一定要使用offer, poll 和 peek 方法替代!。這樣子他就只是給出一個錯誤提示,而不是拋出一個異常。

建議閱讀《java核心技術卷Ⅰ》中 P670 的代碼 ,加深理解 。

Part 2 線程安全的集合

如果多線程要併發地修改一個數據結構,例如散列表,那麼很容易會破壞這個數據結構。 舉個例子:一個線程可能向表中插入一個新的元素,假定在調整散列表各個桶之間的鏈接關係的過程之中,被剝奪了控制權。如果另一個線程也開始遍歷同一個同一個鏈表,很可能無法認出已經在調整之中的鏈接(可能已經無效了)並造成混亂從而拋出異常。

可以通過提供鎖來保護共享數據結構,但是選擇線程安全的實現作爲替代可能更容易一些。當然,前一節討論的阻塞隊列就是線程安全的集合。在下面各小節中,將討論Java類庫提供的另外一些線程安全的集合 。

Part 2.1 高效的映射、集和隊列

java.util.concurrent包提供了映射、有序集、和隊列的高效實現,通過複雜的算法,允許併發的訪問數據結構的不同部分來讓競爭最小化,他們是:ConcurrentHashMap , ConcurrentSkipListMap , ConcurrentSkipListSet , 和 ConcurrentLinkedQueue 。

Part 2.2 併發試圖集
假設你想要的是一個大的線程安全的集而不是映射。我們可以這樣做:

Set<String> words = ConcurrentHashMap.<String>newKeySet();

靜態的newKeySet方法會生成一個Set < k >,實際上是是一個包裝器,所有的映射值都爲Boolean.TRUE,我們把他作爲一個集,不關心他的具體值。

如果在一個線程可能進行修改時要對集合進行迭代,仍然需要使用客戶端上鎖,就是說對集合的操作方法上鎖

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