基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

并发包下的阻塞队列(BlockingQueue)有哪些常见的实现类?

知识点图片

在 Java 的并发包(java.util.concurrent)中,BlockingQueue(阻塞队列)是解决并发生产者-消费者问题的核心组件。它支持在队列为空时阻塞获取元素的线程,在队列满时阻塞添加元素的线程。

常见的 BlockingQueue 实现类有以下 7 种,它们在底层数据结构、有界/无界、锁机制和适用场景上各有不同:


1. ArrayBlockingQueue(基于数组的有界阻塞队列)

  • 底层结构:数组。
  • 是否有界严格有界。必须在创建时指定容量(capacity),且创建后无法修改。
  • 锁机制:使用一把全局 ReentrantLock 锁(出队和入队共用一把锁),因此并发度相对较低。
  • 特点
    • 按照 FIFO(先进先出)原则对元素进行排序。
    • 支持公平锁和非公平锁(默认非公平)。
    • 内存分配连续,不会频繁产生 Node 对象,对 GC 友好。
  • 适用场景:需要严格控制内存大小,且并发量不是极高的场景。

2. LinkedBlockingQueue(基于链表的阻塞队列)

  • 底层结构:单向链表。
  • 是否有界可选有界。默认容量是 Integer.MAX_VALUE(通常被视为无界),也可以在创建时指定容量。
  • 锁机制分离锁(两把锁)。入队使用 putLock,出队使用 takeLock,因此入队和出队可以并发执行,吞吐量通常高于 ArrayBlockingQueue
  • 特点
    • 按照 FIFO 原则排序。
    • 如果是默认容量,在生产者速度远大于消费者速度时,存在耗尽内存(OOM)的风险。
  • 适用场景:高并发的生产者-消费者场景。(最常用的实现类,Executors.newFixedThreadPool() 就使用了它)

3. SynchronousQueue(不存储元素的同步队列)

  • 底层结构:无缓冲。内部不存储任何元素。
  • 是否有界无界/零容量
  • 锁机制:底层通过非阻塞算法(CAS)或特定结构的锁实现。
  • 特点
    • 容量为 0。每一个 put 操作必须等待一个 take 操作,否则无法继续添加元素;反之亦然。
    • 它更像是一个“传递者”,直接将任务从生产者移交给消费者。
    • 支持公平(基于队列)和非公平(基于栈)模式。
  • 适用场景:传递性场景,即生产者和消费者处理速度匹配,或者需要快速响应的场景。Executors.newCachedThreadPool() 使用了它)

4. PriorityBlockingQueue(支持优先级的无界阻塞队列)

  • 底层结构:数组实现的二叉小顶堆。
  • 是否有界无界(容量不够时会自动扩容,直到 MAX_ARRAY_SIZE)。
  • 锁机制:一把全局锁。
  • 特点
    • 不保证 FIFO。元素出队顺序由其自然顺序(实现 Comparable 接口)或提供的 Comparator 决定。
    • 因为是无界队列,put 操作永远不会阻塞,只有当内存耗尽时才会抛出 OutOfMemoryErrortake 操作在队列为空时会阻塞。
  • 适用场景:任务需要区分优先级执行的场景(如 VIP 用户优先处理、紧急警报优先发送)。

5. DelayQueue(支持延迟获取元素的无界阻塞队列)

  • 底层结构:内部封装了 PriorityQueue
  • 是否有界无界
  • 特点
    • 队列中的元素必须实现 Delayed 接口(该接口继承了 Comparable)。
    • 只有当元素的延迟时间(Delay)耗尽时,消费者才能从队列中取走该元素。
    • 内部按照延迟时间的长短进行优先级排序。
  • 适用场景
    • 缓存系统的设计(清理过期缓存)。
    • 定时任务调度(类似 TimerScheduledThreadPoolExecutor 的底层机制)。
    • 订单超时未支付自动取消。

6. LinkedTransferQueue(基于链表的无界传输队列)

  • 底层结构:单向链表。
  • 是否有界无界
  • 特点
    • 实现了 TransferQueue 接口。可以看作是 ConcurrentLinkedQueueSynchronousQueueLinkedBlockingQueue 的超集。
    • 特有方法 transfer(E e):如果当前有消费者正在等待接收元素,生产者会直接把元素传给消费者;如果没有,生产者会将元素放在队列尾部,并阻塞等到该元素被消费者消费后才返回
    • 大量使用了无锁(CAS)操作,性能通常比 LinkedBlockingQueue 更高。
  • 适用场景:要求极高吞吐量且生产者需要知道消息已被消费的场景。

7. LinkedBlockingDeque(基于双向链表的阻塞双端队列)

  • 底层结构:双向链表。
  • 是否有界可选有界(默认 Integer.MAX_VALUE)。
  • 特点
    • 实现了 Deque 接口,支持从队列的两端插入和移出元素(addFirst, addLast, takeFirst, takeLast 等)。
    • 由于是双端操作,多线程并发时,一端入队一端出队可以减少竞争。
  • 适用场景“工作窃取”(Work-Stealing)模式。例如 Fork/Join 框架中,每个线程有自己的双端队列,当自己的任务做完后,可以从其他线程的队列尾部“窃取”任务执行。

💡 总结与选型建议(对比表)

实现类 底层数据结构 有界/无界 锁机制 适用场景
ArrayBlockingQueue 数组 严格有界 单锁 需控制内存,对吞吐量要求非极致的场景
LinkedBlockingQueue 单向链表 可选(默认无界) 双锁(分离锁) 高吞吐量的常规生产者-消费者场景
SynchronousQueue 无缓冲(队列/栈) 零容量 无锁/单锁 需快速交接任务,如 CachedThreadPool
PriorityBlockingQueue 堆(优先队列) 无界 单锁 任务有优先级区分的场景
DelayQueue 优先队列 无界 单锁 延迟执行、定时任务、超时处理
LinkedTransferQueue 单向链表 无界 CAS无锁机制 极高吞吐量,且需确认消息被消费
LinkedBlockingDeque 双向链表 可选(默认无界) 单锁 工作窃取模式,需两端操作的场景

面试高频考点ArrayBlockingQueueLinkedBlockingQueue 的区别(结构、锁机制、内存占用)。实际开发中,强烈建议在使用 LinkedBlockingQueue手动指定容量,防止生产过快导致 OOM。

00:00
00:00