參考資料:http://blog.csdn.net/chenchaofuck1/article/details/51660521
實現一個線程安全的隊列有兩種實現方式:一種是使用阻塞算法,阻塞隊列就是通過使用加鎖的阻塞算法實現的;另一種非阻塞的實現方式則可以使用循環CAS(比較並交換)的方式來實現。
ConcurrentLinkedQueue是一個基於鏈表實現的無界線程安全隊列,它采用先進先出的規則對節點進行排序,當我們添加一個元素的時候,它會添加到隊列的尾部,當我們獲取一個元素時,它會返回隊列頭部的元素。默認情況下head節點存儲的元素為空,tair節點等於head節點。
一:入隊
- 入隊主要做兩件事情,
第一是將入隊節點設置成當前隊列的最后一個節點。
第二是更新tail節點,如果原來的tail節點的next節點不為空,則將tail更新為剛入隊的節點(即隊尾結點),如果原來的tail節點(插入前的tail)的next節點為空,則將入隊節點設置成tail的next節點(而tial不移動,成為倒數第二個節點),所以tail節點不總是尾節點!
public boolean offer(E e) { if (e == null) throw new NullPointerException(); //入隊前,創建一個入隊節點 Node</e><e> n = new Node</e><e>(e); retry: //死循環,入隊不成功反復入隊。 for (;;) { //創建一個指向tail節點的引用 Node</e><e> t = tail; //p用來表示隊列的尾節點,默認情況下等於tail節點。 Node</e><e> p = t; //獲得p節點的下一個節點。 Node</e><e> next = succ(p); //next節點不為空,說明p不是尾節點,需要更新p后在將它指向next節點 if (next != null) { //循環了兩次及其以上,並且當前節點還是不等於尾節點 if (hops > HOPS && t != tail) continue retry; p = next; } //如果p是尾節點,則設置p節點的next節點為入隊節點。 else if (p.casNext(null, n)) { //如果tail節點有大於等於1個next節點,則將入隊節點設置成tair節點,更新失敗了也沒關系,因為失敗了表示有其他線程成功更新了tair節點。 if (hops >= HOPS) casTail(t, n); // 更新tail節點,允許失敗 return true; } // p有next節點,表示p的next節點是尾節點,則重新設置p節點 else { p = succ(p); } } } }
二:出隊
不是每次出隊時都更新head節點,當head節點里有元素時,直接彈出head節點里的元素,而不會更新head節點。只有當head節點里沒有元素時,則彈出head的next結點並更新head結點為原來head的next結點的next結點。
public E poll() { Node</e><e> h = head; // p表示頭節點,需要出隊的節點 Node</e><e> p = h; for (int hops = 0;; hops++) { // 獲取p節點的元素 E item = p.getItem(); // 如果p節點的元素不為空,使用CAS設置p節點引用的元素為null,如果成功則返回p節點的元素。 if (item != null && p.casItem(item, null)) { if (hops >= HOPS) { //將p節點下一個節點設置成head節點 Node</e><e> q = p.getNext(); updateHead(h, (q != null) ? q : p); } return item; } // 如果頭節點的元素為空或頭節點發生了變化,這說明頭節點已經被另外一個線程修改了。那么獲取p節點的下一個節點 Node</e><e> next = succ(p); // 如果p的下一個節點也為空,說明這個隊列已經空了 if (next == null) { // 更新頭節點。 updateHead(h, p); break; } // 如果下一個元素不為空,則將頭節點的下一個節點設置成頭節點 p = next; } return null; }
三:非阻塞卻線程安全的原因
觀察入隊和出隊的源碼可以發現,無論入隊還是出隊,都是在死循環中進行的,也就是說,當一個線程調用了入隊、出隊操作時,會嘗試獲取鏈表的tail、head結點進行插入和刪除操作,而插入和刪除是通過CAS操作實現的,而CAS具有原子性。故此,如果有其他任何一個線程成功執行了插入、刪除都會改變tail/head結點,那么當前線程的插入和刪除操作就會失敗,則通過循環再次定位tail、head結點位置進行插入、刪除,直到成功為止。也就是說,ConcurrentLinkedQueue的線程安全是通過其插入、刪除時采取CAS操作來保證的。不會出現同一個tail結點的next指針被多個同時插入的結點所搶奪的情況出現。