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

相关文章
相关标签/搜索