Java並發容器之非阻塞隊列ConcurrentLinkedQueue


    參考資料: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指針被多個同時插入的結點所搶奪的情況出現。

   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM