`

【Java核心-进阶】并发队列

    博客分类:
  • Java
 
阅读更多

JDK并发队列概览



 

典型特性

Queue vs Deque

Deque 是 双端队列,它的侧重点是支持队列首尾都能添加删除元素。
Deque 提供了更多方法,如典型的 offerLast(e),pollLast()

 

Blocking vs 非Blocking

Blocking表示队列提供了“等待性”相关操作。
如,获取元素时会等到队列中有元素时再返回;添加元素时会等到队列有空间时再添加。
 

/**
 * Inserts the specified element into this queue, waiting if necessary
 * for space to become available.
 */
void put(E e) throws InterruptedException;

/**
 * Retrieves and removes the head of this queue, waiting if necessary
 * until an element becomes available.
 */
E take() throws InterruptedException;

 

有界队列 vs 无界队列

我非常讨厌这样的区分方式,过于强行分类!虽然JDK注释里也使用 bounded/unbounded 这样的形容词,我觉得这种分类方式欠妥;“有界/无界”这种字眼也不适合出现在考题里。

有界还是无界,其实就是队列的容量限制的差别。JDK中真正的无界队列比较少。虽然JDK将某些队列划为无界队列,但其内部数据结构就决定了所谓“无界队列”的上限。
通常,各队列实现的 size() 方法返回的就是 int 类型的值,它们的容量一般不会超过 Integer.MAX_VALUE 。
当然 LinkedTransferQueue 这种特殊队列的元素数量可能超过 Integer.MAX_VALUE。接口 Collection 中对 size() 的定义就允许这种情况。
 

  • ArrayBlockingQueue:最典型的 有界队列。在创建时就需指定容量,且无法更改。
    初始化时就创建内部数组,该数组为 final 字段。
  • LinkedBlockingQueue:即可以是 无界队列,也可以是 有界队列。如果在创建时未指定容量,就会使用默认容量 Integer.MAX_VALUE,成为 无界队列。(所谓有界和无界,其实界限是模糊的)
    该队列内部通过链表实现。单就链表而言,它是无界的。
  • SynchronousQueue:非常特殊的队列,容量为0。看上去就像,每个删除操作都要等待插入操作,每个插入操作都要等待删除操作。即,元素通过此队列从生产者瞬间转移到消费者,而不会在队列中滞留。
    Executors.newCachedThreadPool() 中就用了该队列。
    Dubbo服务的默认线程池队列大小为0,所使用的队列也是 SynchronousQueue
  • PriorityBlockingQueue:这是 无界队列,元素之间有优先顺序。其实现者考虑到各JVM实现细节的差异,将队列的容量上限规定为 Integer.MAX_VALUE - 8。
    其内部使用了 java.util.PriorityQueue
  • DelayedQueue:这是 无界队列。不常用到。其元素必须实现接口 java.util.concurrent.Delayed。只有过期的元素才能被取出。
    其内部使用了 java.util.PriorityQueue
  • LinkedTransferQueue:这是 无界队列。特殊的BlockingQueue,元素的添加者可以等待直到元素被消费者取走。
    其内部基于链表实现

 

如何选择

是否需要“等待性”语义?

如果需要“等待性”语义,则在 BlockingQueue 中选;否则可以考虑 ConcurrentLinkedQueue。
BlockingQueue 最常见的是在 ArrayBlockingQueue、LinkedBlockingQueue 和 SynchronousQueue 之间选择。

 

对队列容量的要求

ArrayBlockingQueue 有明确的容量限制;
LinkedBlockingQueue 也可以在创建时指定一个容量; SynchronousQueue 的容量是 0

 

对空间利用的要求

这方面,ArrayBlockingQueue 与 LinkedBlockingQueue 的区别就是 数组 与 链表 的区别。
ArrayBlockingQueue 更紧凑,不需创建节点;但它在初始化时就需要一次性分配一段连续的空间,对初始内存的需求更大。

 

对吞吐量的要求

ArrayBlockingQueue 中 notEmpty 和 notFull 是对同一个 ReentrantLock 的条件变量。
LinkedBlockingQueue 对锁操作的粒度更细,notEmpty 和 notFull 是不同 ReentrantLock 的条件变量。
所以LinkedBlockingQueue的吞吐量会相对好一点。
这种不同粒度的锁机制其实也是内部实现原理延伸出来的。因为链表结构更容易实现这种细粒度的锁。

 

性能的稳定性

ArrayBlockingQueue 的实现比较简单,它的性能比较好预测,表现比较稳定。
SynchronousQueue 很多时候性能大大超过其它实现,特别是队列元素较少的时候。

 

其它

前述考虑点是比较常见的。
是否需要双端操作(Deque)、是否需要支持元素优先级(PriorityBlockingQueue)、是否需要利用元素过期特性(DelayedQueue)、是否等待消费者获取(TransferQueue)等需求不多见。如果有相关需求,选择也很明确。

 

 

  • 大小: 33.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics