浅谈阻塞队列:Array和Linked与线程池的有机结合

大家好,今天我们来聊聊 Java 中的阻塞队列(BlockingQueue)和它在线程池里的那些事儿。从最基础的概念入手,我们会一步步剖析阻塞队列跟普通队列的关系、常见的两种阻塞队列在加锁和长度上的区别,以及它们在不同线程池中的表现。最终,我们会从一个简单的方案出发,发现问题,再逐步优化,靠近现在最靠谱的线程池配置方式。走起!


1. 阻塞队列和普通队列啥关系?算单列集合吗?

队列是个啥?

说到队列(Queue),简单讲就是个"先来先走"的数据结构,英文叫 FIFO(First In, First Out)。Java 里有个 Queue 接口,定义了基本的操作,比如塞东西(add, offer)、取东西(poll)啥的,大家应该都不陌生。

阻塞队列又是什么?

阻塞队列(BlockingQueue)是 Queue 的"升级版",多了个"堵车"功能。啥意思呢?

  • 如果队列满了,你再往里塞东西,操作就得停下来等着,直到有空位。
  • 如果队列空了,你想拿东西,也得等着,直到有货。

简单来说,它就是在普通队列的基础上加了"耐心等待"的特性,特别适合多线程环境下生产者和消费者之间的协作。

单列集合吗?

是的,阻塞队列属于单列集合。Java 的集合框架分两大家族:

  • Collection:单列集合,比如 List、Set、Queue,存的是一条条独立的数据。
  • Map:双列集合,存的是键值对。

阻塞队列是从 Queue 继承来的,而 QueueCollection 的子接口,所以它妥妥是个单列集合,没跑儿。


2. ArrayBlockingQueue 和 LinkedBlockingQueue 有啥不一样?

Java 里最常见的两种阻塞队列就是 ArrayBlockingQueueLinkedBlockingQueue,咱们从加锁和队列长度两方面掰扯掰扯它们的区别。

ArrayBlockingQueue:数组派

  • 底层咋实现的:用数组存数据。
  • 队列长度:有上限,创建时得定好容量,比如 100 个坑,满了就满了。
  • 加锁咋玩的 :用一个锁(ReentrantLock)管住进出,进队和出队得排队干。

特点

因为只有一个锁,塞东西和拿东西不能一块儿来,谁先抢到锁谁干活儿。适合生产者和消费者节奏差不多的场景。

LinkedBlockingQueue:链表派

  • 底层咋实现的:用链表存数据。
  • 队列长度:灵活得很,可以设上限(比如 1000),也可以不设(理论上无限长)。
  • 加锁咋玩的 :用俩锁(两个 ReentrantLock),一个管塞,一个管拿。

特点

进队和出队各有各的锁,能一块儿干活儿,效率高一些。特别适合生产者和消费者速度不匹配的时候。

区别一览

特点 ArrayBlockingQueue LinkedBlockingQueue
底层结构 数组 链表
队列长度 有上限,得定死 可以有上限,也可以无限
锁的数量 一个锁,进出都抢 俩锁,进出分开管
并发效率 进出得排队 进出能同时干

3. 这些阻塞队列在啥样的线程池里混?

线程池(ThreadPoolExecutor)是 Java 里管线程的大管家,阻塞队列就是它的"任务仓库"。不同的队列会让线程池有不同的脾气,咱们看看这俩家伙都咋用。

ArrayBlockingQueue 在线程池里

  • 特点:容量固定,比如设个 100,满了就塞不下了。
  • 适合啥场景:想严格控制任务堆积数量的时候,避免任务多得炸内存。
  • 咋表现:队列满了,新任务来了就按拒绝策略处理(比如直接扔掉或者抛异常)。

LinkedBlockingQueue 在线程池里

  • 特点:可以有上限,也可以没上限。
  • 适合啥场景
    • 没上限:任务多得吓人,又不想拒绝任务时用。
    • 有上限 :跟 ArrayBlockingQueue 差不多,但效率更高。
  • 咋表现
    • 没上限时,任务随便排队,除非核心线程不够用,不然不会加新线程。
    • 有上限时,满了就加线程(到最大线程数为止),再满就拒绝。

现成的线程池例子

Java 自带了几种线程池,队列用得不一样:

  • FixedThreadPool :用 LinkedBlockingQueue(没上限),线程数固定,任务随便排。
  • CachedThreadPool :用 SynchronousQueue(一种不存货的队列),线程数随便涨。
  • SingleThreadExecutor :也用 LinkedBlockingQueue(没上限),但就一个线程干活。

4. 从简单到牛掰:优化线程池的队列选择

先来个简单方案:ArrayBlockingQueue 开干

咱们假设从一个基础的线程池开始:

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100)
);
  • 核心线程数:5 个
  • 最大线程数:10 个
  • 队列:ArrayBlockingQueue,能装 100 个任务

工作原理

  • 任务来了,先给 5 个核心线程干。
  • 核心线程忙不过来,任务进队列,最多 100 个。
  • 队列满了,再加线程,直到 10 个。
  • 10 个线程还忙不过来,就按拒绝策略处理。

问题在哪儿?

  • 任务多了咋办:队列到 100 就满了,任务堆积受限,容易触发拒绝。
  • 效率咋样:只有一个锁,塞任务和拿任务得排队,忙起来就卡。

不利的地方

  • 任务量大时,队列老满,线程一会儿加一会儿减,太费劲。
  • 一个锁管进出,高峰期容易堵车,效率上不去。

第一步优化:换 LinkedBlockingQueue

试试 LinkedBlockingQueue,双锁效率高:

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)  // 设个 1000 的上限
);
  • 好处:进出任务能一块儿干,效率提升。
  • 容量:1000 个任务的队列,缓冲更多。

还不够?

如果任务量再大点,1000 也不够用,咋办?有人可能会说:"那我干脆用无上限的 LinkedBlockingQueue!"比如:

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>()  // 无上限
);

新问题:线程咋不扩了?

你说得没错!用无上限的 LinkedBlockingQueue,队列永远不会满,因为它能一直往里塞任务。线程池的扩容逻辑是这样的:

  • 核心线程忙不过来,任务进队列。
  • 队列满了,才加线程到最大线程数。

但现在队列没上限,任务全堆队列里了,核心线程数(5 个)就一直干活,永远到不了 10 个最大线程数,除非核心线程忙到炸。这时候内存可能先扛不住,任务堆太多,系统直接崩。

咋解决?

  • 设个合理上限:队列得有个边界,比如 1000 或 5000,看任务量和机器内存定。这样任务多了,队列满后线程能扩到 10 个。
  • 换个队列 :用 SynchronousQueue,它不存任务,任务来了直接推给线程,核心线程忙完就加新线程到最大数。

主流玩法:灵活配置

实际中,LinkedBlockingQueue 是常用选手,但得聪明用:

  • 有上限:比如 1000,控制任务堆积,还能让线程扩容。
  • 无上限:任务量波动大、不想丢任务时用,但得配好监控,别让内存爆。
  • 动态调整:根据业务高峰,调核心线程数、最大线程数和队列容量。

优化后的例子

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)  // 平衡容量和扩容
);
  • 任务少时,5 个核心线程干。
  • 任务多到 1000,队列满,加线程到 10 个。
  • 再多就拒绝,保护系统。

再往前一步:因地制宜选队列

  • SynchronousQueue :任务短平快、要立刻响应,比如 CachedThreadPool,线程随便涨。
  • PriorityBlockingQueue:任务有优先级,按重要性排队。
  • 自定义队列:根据业务写个队列,比如限速的、带权重的。

最后唠唠

阻塞队列是线程池的"任务中转站",选对了能让线程池更顺畅。ArrayBlockingQueue 一个锁、容量固定,适合小规模控制;LinkedBlockingQueue 双锁、容量灵活,高并发更给力。从简单的 ArrayBlockingQueue 出发,我们发现容量和效率的短板,换上 LinkedBlockingQueue,再解决无上限不扩容的问题,慢慢摸到了现在的主流配置:合理容量 + 灵活扩容。

相关推荐
向哆哆8 分钟前
Spring Boot快速开发:从零开始搭建一个企业级应用
java·spring boot·后端
[email protected]1 小时前
ASP.NET Core 中实现 Markdown 渲染中间件
后端·中间件·asp.net·.netcore
eternal__day6 小时前
Spring Boot 实现验证码生成与校验:从零开始构建安全登录系统
java·spring boot·后端·安全·java-ee·学习方法
海天胜景8 小时前
HTTP Error 500.31 - Failed to load ASP.NET Core runtime
后端·asp.net
海天胜景8 小时前
Asp.Net Core IIS发布后PUT、DELETE请求错误405
数据库·后端·asp.net
源码云商10 小时前
Spring Boot + Vue 实现在线视频教育平台
vue.js·spring boot·后端
RunsenLIu11 小时前
基于Django实现的篮球论坛管理系统
后端·python·django
HelloZheQ13 小时前
Go:简洁高效,构建现代应用的利器
开发语言·后端·golang
caihuayuan513 小时前
[数据库之十四] 数据库索引之位图索引
java·大数据·spring boot·后端·课程设计
风象南14 小时前
Redis中6种缓存更新策略
redis·后端