<>有感 ConcurrentLinkedQueue解讀

ConcurrentLinkedQueue(上集)

算法實現 CAS

  • CAS的優勢

    當一個線程執行任務失敗不影響其餘線程的進行 最大限度的利用CPU資源 能提升程序的伸縮性 伸縮性:不修改任何代碼 升級硬件就能帶來性能上的提升 升級硬件帶來的性能提升明顯 就是伸縮性良好java

  • CAS的缺點

    代碼複雜 影響閱讀性 剛開始看ConcurrentLinkedQueue的時候 沒有正確的思路,理解起來會比較費勁 我推薦直接用多線程同時執行的方式去理解 這樣會比較好node

重要概念

  • 不變性

    • 全部item不爲null的節點都能從head頭節點開始經過succ()方法訪問到
    • head!=null 只要隊列有值 保證真實的head永不爲null head哪怕會自引用 早晚也會解除這種假狀態
  • 可變性

    • heatd.item 可能爲null也可能不爲null 由於cas活鎖操做 每一行代碼執行都不影響其餘線程的訪問相同的代碼塊
    • tail尾節點的更新是滯後於head的 我的理解 在offer中 尾節點掉隊後 經過head節點 (不變性1的保證) 成功訪問最後一個p.next=null的節點
  • 快照

    • snapshot是我本身的理解 由於對於多線程操做來講 當前引用對象 如offer()中 t=tail中的t; p=t中的p; q=p.next中的q都是一個快照 他得到一個對象的快照版本 而後在後續的操做中 使(t!=(t=tail))這樣操做有意義

重要方法

  • offer()入隊
  • poll() 出隊

源碼

public boolean offer(E e) {
        checkNotNull(e); //NullPointException檢查   
        final Node<E> newNode = new Node<E>(e); //包裝成一個Node對象

        for (Node<E> t = tail, p = t;;) {//獲取當前尾節點 t=tail,p是真正的尾節點 p.next==null 
            Node<E> q = p.next;
            if (q == null) {
                // p is last node 
                if (p.casNext(null, newNode)) {//方法1 CAS更新 本身想3個線程同時進行這個操做
                    // Successful CAS is the linearization point
                    // for e to become an element of this queue,
                    // and for newNode to become "live".
                    if (p != t) // hop two nodes at a time //方法2 延遲更新尾節點 下面說爲何
                        casTail(t, newNode);  //方法3 成不成功無所謂 下面說
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)// 方法4 學習offer方法時 能夠暫時放棄這一步
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else  //去找到真正的尾節點 此處和方法2 應是相互輝映的存在
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q; //方法5
        }
    }

解讀offer()方法

自頂向下 思考CAS中可能出現的狀況 CAS是活鎖 所謂活鎖便是每一行代碼運行時 容許其餘線程訪問相同的代碼塊 成功與失敗並存 衍生了更多的條件判斷 本人以爲CAS方法都應該從這個方法去理解 再本身畫畫時序圖 (注意:理解offer()時,先把方法4排除,由於4方法出現自引用的狀況 只有offer()poll()交替執行時會出現 本文只介紹第一種狀況)算法

  • 多線程操做多線程

    1. 第一種狀況: 只有 offer()
    2. 第二種狀況: offer()poll()方法交替執行
  • 同時執行offer()(假設咱們如今有3個線程)併發

    • 不變性:永遠只有一個線程CAS成功 而且總會成功一個
    • 循環次數分析:Thread1 成功 循環一次退出 Thread2失敗 再循環一次成功 Thread3失敗 再循環兩次成功 若是有n個線程同時執行 offer() 執行次數 最大爲n次 最少爲1次
    • 方法5中三目表達式解析: p=condition?result1:result2 我先說一下這裏的意義 知足result1的場景爲 :獲取尾節點tail的快照已通過時了(其餘線程更新了新的尾節點tail) 直接跳轉到當前得到的最新尾節點的地方 知足result2的場景爲:多線程同時操做offer() 執行1方法CAS成功後 未更新尾節點(未執行3方法:兩種緣由 1是未知足前置條件if判斷 2是CAS更新失敗) 直接找next節點
    • 方法2與方法5 是整個offer() 操做的點睛之筆 下面解釋
  1. 只有offer() 操做時高併發

    假設:

    Thread 1執行完1方法成功 還未執行2方法 Thread2和Thread3進入5方法 ,也就是說Thread2和Thread3執行5方法發生在Thread1執行2方法以前 Thread2 and Thread3 invoke method5() before Thread1 invoke method2() 性能

    此時 Thread2.p =q,Thread3.p=q, 由於p==t成立 時序圖以下,而後Thread1執行方法2 p==t 不執行tail尾節點的更新操做 由此可知 尾節點是延遲更新 一切爲了更高效~~~學習

    圖1

    圖1

Thread 2 與 Thread3 此時再次執行 1 方法 見圖1 他們此時的q.next==null 咱們規定Thread2 CAS成功 Thread3失敗了 成功後的時序圖以下 咱們假設 Thread3 invoke method5() after Thread2 invoke method2() Thread2執行方法2 在 Thread3執行方法5以前this

圖2

圖2

對於Thread2 進入2方法 p!=t 知足 執行 casTail(t, newNode) 更新尾節點的快照 以下圖spa

圖3

圖3

Thread2 工做完成 退出循環

對於Thread3 由於執行1方法失敗 進入5方法 此時Thread3的tail快照t3

p = (p != t && t != (t = tail)) ? t : q;

按圖3來翻譯

p=(p!=t3&&t3!=(t3=t2))?t2:q;

p=t2;//直接去當前能獲取到的尾節點!!!

到這裏 offer() 方法解決完成

ConcurrentLinkedQueue核心總結

  • tail和head都是 延遲更新的 可是tail更新在head更新後面 由於方法4中 須要依賴head節點 去找每個存活的節點
  • 前面的敘述中 能夠看到 offer() 方法內 核心操做 就是 p=condition?result1:result2
  • 偶數次offer() 操做更新一次tail 單線程的環境下

與Michael-Scott 隊列比較

  • Michael-Scott隊列 每次操做 都須要判斷是否須要推進尾節點 採起CAS的操做 優勢也是缺點
  • Doug Lead老神仙的CAS 我這個菜鳥猜想 能不用CAS 就儘可能不用 由於CAS存在競爭 提供以最少次數的更新達到最終正確的效果
  • 咱們把offer()中的整個行爲想象爲跳臺階 result1的形式就像是 武俠小說中的越階戰鬥!!!result2的形式就是一步一個腳印 每次平穩地去下一個臺階
  • 咱們想象一下 offer()最優的狀況 10個線程同時offer()

    每個執行1方法成功的線程都沒有(執行2方法或則執行3方法失敗) 不要緊 尾節點的更新終會成功

    每個失敗的線程都是去當前節點的next節點 p.next進行插入操做 在第9個線程(至關於咱們上文中的線程2)

    當第10個線程操做時 雖然它很可憐 一直排到最後 可是尾節點更新一下就越過了9階!!!(不太恰當的地方請大佬們指點) 

  • ConcurrrntLinkedQueue 優勢

    1. 能躍過一整段由於多線程在極短期內offer()插入的節點 直接去尾節點 直接跨過去
    2. 能抵達每個相對於當前快照來講最新的next節點
    3. 高併發時 tail 和 p 相互配合 盡力去離當前尾節點 最近的地方
  • ConcurrentLinkedQueue 缺點

    1. CAS操做 雖然總會成功 可是競爭效率若是很低 不如用同步鎖 採用CAS編寫併發代碼 都是大佬級別 難度高 不接地氣(嘿嘿)
    2. 循環可能會帶來額外的資源開銷
相關文章
相關標籤/搜索