寫這篇文章源於我經歷過的一次生產事故,在某家公司的時候,有個服務會收集業務系統的日誌,此服務的開發人員在給業務系統的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