写这篇文章源于我经历过的一次生产事故,在某家公司的时候,有个服务会收集业务系统的日志,此服务的开发人员在给业务系统的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