Lesson2.1:LinkedList、ConcurrentLinkedQueue、LinkedBlockingQueue對比分析

      寫這篇文章源於我經歷過的一次生產事故,在某家公司的時候,有個服務會收集業務系統的日誌,此服務的開發人員在給業務系統的sdk中就由於使用了LinkedList,又沒有作併發控制,就形成了此服務常常不能正常收集到業務系統的日誌(丟日誌以及日誌上報的線程中止運行)。看一下add()方法的源碼,咱們就能夠知道緣由了:java

    public boolean add(E e) {
        linkLast(e);//調用linkLast,在隊列尾部添加元素
        return true;
    }

    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;//多線程狀況下,若是業務系統沒作併發控制,size的數量會遠遠大於實際元素的數量
        modCount++;
    }

    demo  Lesson2LinkedListThreads 展現了在多線程且沒有作併發控制的環境下,size的值遠遠大於了隊列的實際值,100個線程,每一個添加1000個元素,最後實際只加進去2030個元素:node

         List的變量size值爲:88371
         第2031個元素取出爲null算法

    解決方案,使用鎖或者使用ConcurrentLinkedQueue、LinkedBlockingQueue等支持添加元素爲原子操做的隊列。服務器

    上一節咱們已經分析過LinkedBlockingQueue的put等方法的源碼,是使用ReentrantLock來實現的添加元素原子操做。咱們再簡單看一下高併發queue的add和offer()方法,方法中使用了CAS來實現的無鎖的原子操做:多線程

   public boolean add(E e) {
       return offer(e);
     }併發


public boolean offer(E e) { checkNotNull(e); final Node<E> newNode = new Node<E>(e); for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; if (q == null) { // p is last node if (p.casNext(null, newNode)) { // 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 casTail(t, newNode); // Failure is OK. return true; } // Lost CAS race to another thread; re-read next } else if (p == q) // 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 // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } }

  接下來,咱們再利用高併發queue對上面的demo進行改造,你們只要改變demo中的內容,講下面兩行的註釋內容顛倒,便可發現沒有丟失任何的元素:高併發

      public static LinkedList list = new LinkedList();
      //public static ConcurrentLinkedQueue list = new ConcurrentLinkedQueue();性能

     再看一下高性能queue的poll()方法,才以爲NB,取元素的方法也用CAS實現了原子操做,所以在實際使用的過程當中,當咱們在不那麼在乎元素處理順序的狀況下,隊列元素的消費者,徹底能夠是多個,不會丟任何數據:測試

    public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    // Successful CAS is the linearization point
                    // for item to be removed from this queue.
                    if (p != h) // hop two nodes at a time
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

    關於ConcurrentLinkedQueue和LinkedBlockingQueue:this

    1.LinkedBlockingQueue是使用鎖機制,ConcurrentLinkedQueue是使用CAS算法,雖然LinkedBlockingQueue的底層獲取鎖也是使用的CAS算法

    2.關於取元素,ConcurrentLinkedQueue不支持阻塞去取元素,LinkedBlockingQueue支持阻塞的take()方法,如若你們須要ConcurrentLinkedQueue的消費者產生阻塞效果,須要自行實現

    3.關於插入元素的性能,從字面上和代碼簡單的分析來看ConcurrentLinkedQueue確定是最快的,可是這個也要看具體的測試場景,我作了兩個簡單的demo作測試,測試的結果以下,兩個的性能差很少,但在實際的使用過程當中,尤爲在多cpu的服務器上,有鎖和無鎖的差距便體現出來了,ConcurrentLinkedQueue會比LinkedBlockingQueue快不少:

demo Lesson2ConcurrentLinkedQueuePerform:在使用ConcurrentLinkedQueue的狀況下100個線程循環增長的元素數爲:33828193

demo Lesson2LinkedBlockingQueuePerform:在使用LinkedBlockingQueue的狀況下100個線程循環增長的元素數爲:33827382

相關文章
相關標籤/搜索