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

JDK并发队列概览

典型特性

Queue vs Deque

Deque 是 双端队列,它的侧重点是支持队列首尾都能添加删除元素。

Deque 提供了更多方法,如典型的 offerLast(e),pollLast()

Blocking vs 非Blocking

Blocking表示队列提供了“等待性”相关操作。

如,获取元素时会等到队列中有元素时再返回;添加元素时会等到队列有空间时再添加。

Java代码

 

  1. /** 

    扫描二维码关注公众号,回复: 8587570 查看本文章
  2.  * Inserts the specified element into this queue, waiting if necessary 

  3.  * for space to become available. 

  4.  */  

  5. void put(E e) throws InterruptedException;  

  6.   

  7. /** 

  8.  * Retrieves and removes the head of this queue, waiting if necessary 

  9.  * until an element becomes available. 

  10.  */  

  11. 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)等需求不多见。如果有相关需求,选择也很明确。

发布了219 篇原创文章 · 获赞 3 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/hchaoh/article/details/103904922