深刻浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque[转]

这个小节介绍Queue的最后一个工具,也是最强大的一个工具。从名称上就能够看到此工具的特色:双向并发阻塞队列。所谓双向是指能够从队列的头和尾同时操做,并发只是线程安全的实现,阻塞容许在入队出队不知足条件时挂起线程,这里说的队列是指支持FIFO/FILO实现的链表。java

 

首先看下LinkedBlockingDeque的数据结构。一般状况下从数据结构上就能看出这种实现的优缺点,这样就知道如何更好的使用工具了。安全

LinkedBlockingDeque类图

从数据结构和功能需求上能够获得如下结论:数据结构

  1. 要想支持阻塞功能,队列的容量必定是固定的,不然没法在入队的时候挂起线程。也就是capacity是final类型的。
  2. 既然是双向链表,每个结点就须要先后两个引用,这样才能将全部元素串联起来,支持双向遍历。也即须要prev/next两个引用。
  3. 双向链表须要头尾同时操做,因此须要first/last两个节点,固然能够参考LinkedList那样采用一个节点的双向来完成,那样实现起来就稍微麻烦点。
  4. 既然要支持阻塞功能,就须要锁和条件变量来挂起线程。这里使用一个锁两个条件变量来完成此功能。

 

有了上面的结论再来研究LinkedBlockingDeque的优缺点。并发

优势固然是功能足够强大,同时因为采用一个独占锁,所以实现起来也比较简单。全部对队列的操做都加锁就能够完成。同时独占锁也可以很好的支持双向阻塞的特性。高并发

凡事有利必有弊。缺点就是因为独占锁,因此不能同时进行两个操做,这样性能上就大打折扣。从性能的角度讲LinkedBlockingDeque要比LinkedBlockingQueue要低不少,比CocurrentLinkedQueue就低更多了,这在高并发状况下就比较明显了。工具

前面分析足够多的Queue实现后,LinkedBlockingDeque的原理和实现就不值得一提了,无非是在独占锁下对一个链表的普通操做。性能

有趣的是此类支持序列化,可是Node并不支持序列化,所以fist/last就不能序列化,那么如何完成序列化/反序列化过程呢?.net

清单1 LinkedBlockingDeque的序列化、反序列化线程

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    lock.lock();
    try {
        // Write out capacity and any hidden stuff
        s.defaultWriteObject();
        // Write out all elements in the proper order.
        for (Node<E> p = first; p != null; p = p.next)
            s.writeObject(p.item);
        // Use trailing null as sentinel
        s.writeObject(null);
    } finally {
        lock.unlock();
    }
}对象

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    count = 0;
    first = null;
    last = null;
    // Read in all elements and place in queue
    for (;;) {
        E item = (E)s.readObject();
        if (item == null)
            break;
        add(item);
    }
}

 

清单1 描述的是LinkedBlockingDeque序列化/反序列化的过程。序列化时将真正的元素写入输出流,最后还写入了一个null。读取的时候将全部对象列表读出来,若是读取到一个null就表示结束。这就是为何写入的时候写入一个null的缘由,由于没有将count写入流,因此就靠null来表示结束,省一个整数空间。

相关文章
相关标签/搜索