Skip to main content

11小节:实现阻塞队列容量热更新策略

作者:程序员马丁

在线博客:https://nageoffer.com

note

热门项目实战社群,收获国内众多知名公司面试青睐,近千名同学面试成功!助力你在校招或社招上拿个offer。

实现阻塞队列容量热更新策略,元数据信息:

©版权所有 - 拿个offer-开源&项目实战星球专属学习项目,依据《中华人民共和国著作权法实施条例》《知识星球产权保护》,严禁未经本项目原作者明确书面授权擅自分享至 GitHub、Gitee 等任何开放平台。违者将面临法律追究。


内容摘要:在日常开发中,我们常通过调整线程池参数来优化系统性能,然而你是否遇到过这样的场景:线程池任务堆积、队列打满,却无法重启服务,只想临时调大队列容量以缓解压力?本篇文章将聚焦于阻塞队列容量为何不能动态调整,以及我们该如何优雅地实现一套支持运行时热更新容量的阻塞队列方案。

课程目录如下所示:

  • 业务说明
  • 如何实现阻塞队列热更新?
  • 文末总结

业务说明

1. 什么是队列?

队列是一种遵循 先进先出(FIFO) 规则的线性数据结构:元素只能从“队尾”入队、在“队头”出队;当内部没有元素时即为空队列。凭借这一简单而强大的特性,队列在程序设计中无处不在——从线程池的任务排队到消息中间件的核心存储模型,都离不开它。

2. 什么是阻塞队列?

队列大家都不陌生,那 阻塞队列 又是什么?在 Java 中它由接口 BlockingQueue 定义,虽然名称看起来抽象,底层实现却十分灵活——可以基于数组,也可以使用单向或双向链表等结构。

与普通队列相比,阻塞队列多了两项关键能力:

  1. 阻塞插入:当队列已满时,执行 put 的线程会被挂起,直到出现空位;
  2. 阻塞移除:当队列为空时,执行 take 的线程同样会被挂起,直到有元素可取。

LinkedBlockingQueue 就是其中一种实现,内部采用单向链表存储元素。下面的继承关系图展示了它在 Java 并发包中的层次结构。

这里不针对阻塞队列展开具体介绍,网上的资料也挺多的,我在 21 年写过一篇 1.1w字,10图,轻松掌握BlockingQueue,推荐大家学习下。

JDK 默认提供了以下几种阻塞队列实现:

如何实现阻塞队列热更新?

1. 阻塞队列不支持更新容量

在日常线程池调优过程中,我们可能会遇到一个真实的问题:

队列被塞满了,线程池也跑满了,但我又不能轻易重启服务,只是想把队列容量调大点,临时抗一波压力,有没有办法?

如果使用的是 LinkedBlockingQueue,可能会发现它的容量是固定的,根本不支持动态调整——这就是我们今天要讲的问题。

LinkedBlockingQueue 阻塞队列的容量是 int 类型,如果不设置容量默认是 Integer.MAX_VALUE,设置容量后,因为字段类型是 final,是没有办法变更容量的。

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {

/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;
}

2. 动态变更阻塞队列场景

讲具体原理前,还是要和大家聊一下,阻塞队列动态变更是否有用?

如果阻塞队列容量固定,遇到访问量超过预期,可能很快就会被打满,造成任务拒绝。而如果能在运行时动态调大阻塞队列容量,就能临时缓解系统压力,避免雪崩。

举例:默认线程池队列容量为 1000。在访问激增或黑产攻击爆发时,通过管理平台将队列扩容至 10000,有效缓冲流量高峰,避免拒绝关键任务。

那可能有同学问了:为什么不直接调大线程池,而是调整阻塞队列容量?这就涉及到两者调整的预期目标:

  • 调大线程:提高并发执行能力,前提是底层资源能够承受这么大的并发。要不然出问题就是雪崩了。
  • 调大阻塞队列容量:增强任务缓冲能力(更多任务排队),可能会慢处理,但是会最终处理。属于是通过空间换高可用一种方案。
调参方式核心作用风险/代价适用场景
增加线程池大小增加并发处理能力高:CPU竞争、内存压力、线程切换开销CPU 不敏感、I/O 密集型或延迟敏感场景
增加队列容量增加任务缓冲能力低:只是任务排队变多,但延迟可能增加弹性应对流量突发、避免任务被拒绝或丢失

这里额外提一下,线程不是越多越好,线程切换、上下文切换、内存开销都是真金白银的成本。

解锁付费内容,👉 戳