要實現乙個執行緒安全的佇列有兩個方式:一種是使用阻塞演算法,另一種是使用非阻塞演算法。
阻塞演算法:
使用阻塞演算法的佇列可以用乙個鎖(入隊和出隊同一把鎖)或兩把鎖(入隊和出隊用不同的鎖)來實現。
非阻塞的實現方式則可以使用迴圈cas的方式來實現。
concurrentlinkedqueue非阻塞執行緒安全佇列
concurrentlinkedqueue是乙個基於鏈結節點的無界執行緒安全的佇列。
它採用fifo(先進先出)的規則對節點進行排序;
當我們新增乙個元素的時候,它會新增到佇列的尾部;
當我們獲取乙個元素時,它會返回佇列頭部的元素。
佇列中不允許null元素。
判斷佇列成員是否為空,不要使用size()方法,使用isempty()方法,因為size()方法會遍歷整個佇列。
concurrentlinkedqueue由head節點和tail節點組成,每個節點(node)由節點元素(item)和指向下乙個節點的引用(next)組成;
節點與節點之間就是通過這個next關聯起來,從而形成乙個鍊錶結構的佇列。
我們看下原始碼中的引數:
private transient volatile nodehead;
private transient volatile nodetail;
public concurrentlinkedqueue()
要是你看過hahsmap的原始碼,會發現,這個和entry鍊錶差不多..
預設情況下head節點儲存的元素為空,tail節點等於head節點
我們看下node的原始碼:
private static class node
boolean casitem(e cmp, e val)
void lazysetnext(nodeval)
boolean casnext(nodecmp, nodeval)
.....
}
concurrentlinkedqueue入隊操作
入佇列就是將新新增的node節點,新增到佇列的尾部。
在沒新增元素之前,head和tail都是node自己(head節點)。
a. 新增元素1:佇列更新head節點的next節點為"元素1節點",又因為tail節點預設情況下等於head節點,所以他們的next節點都指向"元素1節點"
b. 新增元素2:佇列首先設定"元素節點1"的next節點為"元素2節點",然後更新tail節點指向"元素2節點"
c. 新增元素3:設定tail節點為元素3節點
d. 新增元素4:設定元素3的next節點為元素4節點,然後將tail節點指向元素4節點。
通過觀察入隊過程以及head節點和tail節點的變化,發現入隊主要做兩件事:
第二:更新tail節點,如果tail節點的next節點不為空,則將入隊節點設定成tail節點,如果tail節點的next節點為空,則將入隊節點設定成tail的next節點,所以tail節點不總是尾節點。(理解這點很重要...)
(讓tail節點永遠作為佇列的尾節點,這樣實現的**量非常少,而且邏輯清晰、易懂,但是有個缺點,每次都要使用迴圈cas更新tail節點。
如果能減少cas更新tail節點的次數,就能提高入隊的效率;so,並不是每次節點入隊後都將tail節點更新成尾節點;而且隨著佇列長度越來越大,
每次入隊時定位尾節點的時間就越長,太消耗效能..)
我們來看下原始碼:
public boolean offer(e e)
} // 如果p和它的下乙個節點相等。
// 則說明p節點和q節點都為空,表示佇列剛剛初始化,所以返回head節點
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
// p有next節點,表示p的next節點是尾節點,則需要重新更新p後,將它指向next節點
p = (p != t && t != (t = tail)) ? t : q;
}}
(此原始碼來自jdk1.7,與原書有差別)
從原始碼角度來看,整個入隊過程主要做兩件事:
1. 定位出尾節點;
tail節點並不總是尾節點,所以每次入隊都必須通過tail節點來找到尾節點。
尾節點可能是tail節點也可能是tail節點的next節點
2. 使用cas演算法將入隊節點設定成尾節點的next節點,如不成功則重試。
concurrentlinkedqueue出佇列
出佇列就是從佇列中返回乙個節點元素,並清空該節點對元素的引用。
觀察上圖可以發現,並不是每次出佇列時都更新head節點,當head節點有元素時,直接彈出head節點中的元素,而不會更新head節點。
只有當head節點中沒有時,出隊操作才會更新head節點。
主要是,儘量減少cas更新head節點的消耗,這種做法可以提高出隊的效率。
concurrentlinkedqueue使用demo
public class concurrentlinkedqueuetest
latch.await();
system.out.println("cost time : " + (system.currenttimemillis() - starttime) + " ms");
service.shutdown();
} /**
* 生產者
*/public static void offer()
} /**
* 消費
* * @author cyx
* @time 2023年7月31日上午9:34:02
*/static class pool implements runnable
latch.countdown();
} }}
併發程式設計9 併發容器
解決併發情況下的容器執行緒安全問題 譬如 vector,hashtable,都是給予同步鎖實現的 concurrent包下的類,大部分都是使用系統底層的實現,類似於native public class test09 latch.countdown for thread t arr try catc...
併發容器3
上篇部落格針對快取用個futuretask 來進行處理來解決兩個執行緒可能計算出來同樣的值的問題。在上篇部落格中的實現幾乎是完美的,它能夠表現出非常好的併發性,如果結果計算出來則立即返回,如果其他執行緒在計算該結果,那麼新的執行緒將一直等待這個結果被計算出來。這樣其實也沒有徹徹底底的解決兩個執行緒計...
Java併發容器
同步容器將所有容器狀態的訪問都序列化,以實現執行緒安全性。這種方式的代價會嚴重降低併發性,多個執行緒競爭容器的鎖時,吞吐量將嚴重降低。併發容器是針對多個執行緒併發訪問設計的。通過併發容器來代替同步容器,可以極大地提高伸縮性並降低風險。與hashmap一樣,concurrenhashmap也是乙個基於...