BlockingQueue(阻塞队列)是 Java 并发编程的核心组件,它既是「队列」(存储元素),又具备「阻塞」特性:当队列空时,取元素的线程会阻塞;当队列满时,存元素的线程会阻塞。这种特性让它成为线程池、生产者-消费者模型的核心底层依赖,本文会从「核心原理、核心实现类、底层机制、使用场景」四个维度,把 BlockingQueue 讲透。

一、核心概念:BlockingQueue 基础

1. 核心接口定义

BlockingQueue 继承自 Queue 接口,扩展了阻塞式的入队/出队方法,核心方法分为三类(以 put/take 为核心):

方法类型

入队方法

出队方法

特点

阻塞式(核心)

put(E e)

take()

队列满时 put 阻塞,队列空时 take 阻塞,直到条件满足

超时阻塞式

offer(E e, long timeout, TimeUnit unit)

poll(long timeout, TimeUnit unit)

阻塞指定时间,超时后返回 false(入队)/null(出队)

非阻塞式

offer(E e)

poll()

队列满时 offer 返回 false,队列空时 poll 返回 null(不阻塞)

2. 核心特性

线程安全:所有方法都保证线程安全,底层通过锁(ReentrantLock)实现;

阻塞语义:解决生产者-消费者的同步问题,无需手动加锁/唤醒;

不允许 null 元素:所有实现类都拒绝插入 null,避免与「队列空返回 null」的语义冲突。

二、核心实现类:7 种阻塞队列的底层原理

JDK 提供了 7 种 BlockingQueue 实现,核心可分为「有界/无界」「阻塞/非阻塞(并发)」「普通/优先级」三类,下面重点讲解最常用的 5 种:

1. ArrayBlockingQueue:数组实现的有界阻塞队列

底层原理

存储结构:基于「定长数组」实现,初始化时必须指定容量(严格有界);

锁机制:使用 单一 ReentrantLock + 两个 Condition(notEmpty/notFull)实现阻塞:

put 方法:获取锁后,若队列满则调用 notFull.await() 阻塞,直到有线程取元素并唤醒 notFull;

take 方法:获取锁后,若队列空则调用 notEmpty.await() 阻塞,直到有线程存元素并唤醒 notEmpty;

公平性:支持公平/非公平锁(默认非公平),公平锁会按线程等待顺序唤醒,避免饥饿,但性能略低。

核心源码(put/take 简化版)

public class ArrayBlockingQueue extends AbstractQueue implements BlockingQueue {

private final Object[] items; // 定长数组存储元素

private final ReentrantLock lock; // 全局锁

private final Condition notEmpty; // 队列非空条件(唤醒取元素的线程)

private final Condition notFull; // 队列非满条件(唤醒存元素的线程)

public ArrayBlockingQueue(int capacity) {

this(capacity, false); // 默认非公平锁

}

// 阻塞入队

public void put(E e) throws InterruptedException {

checkNotNull(e);

final ReentrantLock lock = this.lock;

lock.lockInterruptibly(); // 可中断的锁

try {

while (count == items.length) { // 队列满,循环检查(防止虚假唤醒)

notFull.await(); // 阻塞,释放锁

}

enqueue(e); // 入队

notEmpty.signal(); // 唤醒取元素的线程

} finally {

lock.unlock();

}

}

// 阻塞出队

public E take() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == 0) { // 队列空,循环检查

notEmpty.await(); // 阻塞

}

E e = dequeue(); // 出队

notFull.signal(); // 唤醒存元素的线程

return e;

} finally {

lock.unlock();

}

}

}

核心特点

严格有界,初始化后容量不可变;

数组实现,查找/遍历效率高;

单一锁,存/取操作互斥(高并发下性能略低)。

2. LinkedBlockingQueue:链表实现的阻塞队列(默认无界)

底层原理

存储结构:基于「单向链表」实现,默认容量为 Integer.MAX_VALUE(无界),也可手动指定容量(有界);

锁机制:使用 两把分离的 ReentrantLock(putLock/takeLock)+ 各自的 Condition:

putLock:控制存元素,关联 notFull Condition;

takeLock:控制取元素,关联 notEmpty Condition;

并发优势:存/取操作使用不同的锁,可同时进行(ArrayBlockingQueue 是互斥的),高并发下性能更高。

核心特点

可指定有界/无界(推荐手动指定容量,避免无界队列内存溢出);

链表实现,插入/删除效率高;

分离锁,存/取并发执行,性能优于 ArrayBlockingQueue。

3. SynchronousQueue:无存储的同步队列

底层原理

存储结构:无实际存储结构(容量为 0),本质是「线程配对」工具:

存元素(put)的线程必须等待取元素(take)的线程,反之亦然;

一个线程的 put 必须匹配另一个线程的 take,否则阻塞;

实现方式:支持「公平/非公平」模式(默认非公平),底层基于 TransferQueue 实现(JDK 1.7 后)。

核心特点

无容量,不存储元素,仅做线程间的直接数据传递;

性能极高(无队列存储开销);

是 Executors.newCachedThreadPool() 的默认队列(核心线程 0,非核心线程无上限,依赖 SynchronousQueue 直接传递任务)。

4. PriorityBlockingQueue:优先级阻塞队列

底层原理

存储结构:基于「二叉堆」(数组实现),元素按优先级排序(默认自然序,可自定义 Comparator);

锁机制:单一 ReentrantLock + notEmpty Condition(无 notFull,因为是无界队列);

无界特性:容量默认 11,满时自动扩容,不会阻塞存元素的线程,仅在队列为空时阻塞取元素的线程。

核心特点

无界队列,元素按优先级排序;

不保证同优先级元素的顺序;

适合“按优先级处理任务”的场景(如任务调度)。

5. DelayQueue:延迟阻塞队列

底层原理

存储结构:基于 PriorityBlockingQueue 实现,元素必须实现 Delayed 接口(重写 getDelay(TimeUnit) 和 compareTo 方法);

核心逻辑:仅当元素的「延迟时间到期」后,才能被取出;队首元素是延迟时间最短的元素;

阻塞机制:take 方法会检查队首元素的延迟时间,未到期则阻塞,直到延迟时间到或被中断。

核心特点

无界队列,元素按延迟时间排序;

元素必须实现 Delayed 接口;

适合“定时任务、缓存过期清理、消息延迟推送”等场景。

6. LinkedTransferQueue:链表实现的TransferQueue(扩展)

基于链表的无界队列,扩展了 TransferQueue 接口,支持「直接传递」:transfer(E e) 方法会阻塞,直到元素被消费;

性能优于 LinkedBlockingQueue,是 JDK 1.7 新增,适合“生产者必须等待消费者处理完元素”的场景。

7. LinkedBlockingDeque:双向阻塞队列

基于双向链表实现,支持从队首/队尾存/取元素,适合“双端操作”的场景(如工作窃取线程池 ForkJoinPool)。

三、BlockingQueue 核心底层机制

1. 阻塞的实现:Condition 等待/唤醒

所有阻塞队列的「阻塞」特性都基于 Condition 实现,核心逻辑是:

存元素时,若队列满 → 调用 notFull.await() 释放锁并阻塞;

取元素时,若队列空 → 调用 notEmpty.await() 释放锁并阻塞;

取元素后,唤醒 notFull(队列有空间);存元素后,唤醒 notEmpty(队列有元素);

使用 while 循环检查条件(而非 if),防止「虚假唤醒」(操作系统层面的伪唤醒)。

2. 线程安全的实现:ReentrantLock

ArrayBlockingQueue:单一锁,存/取互斥;

LinkedBlockingQueue:分离锁,存/取并发;

所有锁默认非公平(性能更高),ArrayBlockingQueue 支持手动指定公平锁。

3. 有界 vs 无界的核心区别

类型

代表实现类

核心特点

有界

ArrayBlockingQueue、指定容量的 LinkedBlockingQueue

容量固定,存满后阻塞,可控性强,避免内存溢出

无界

LinkedBlockingQueue(默认)、PriorityBlockingQueue、DelayQueue

容量默认 Integer.MAX_VALUE,存元素不会阻塞,可能导致 OOM(内存溢出)

四、各阻塞队列的使用场景(核心)

1. ArrayBlockingQueue

适用场景:需要「严格控制队列容量」的场景,比如固定大小的生产者-消费者模型、资源受限的线程池;

典型案例:金融交易系统(控制并发请求数,避免系统过载);

优点:数组实现,内存连续,遍历/查找效率高;缺点:单一锁,高并发下存/取互斥。

2. LinkedBlockingQueue

适用场景:高并发的生产者-消费者模型、线程池(Executors.newFixedThreadPool() 默认队列);

典型案例:电商订单处理(高并发下存/取并发执行,提升吞吐量);

注意:务必手动指定容量,避免默认无界导致 OOM。

3. SynchronousQueue

适用场景:需要「任务直接传递」的场景,比如缓存线程池(Executors.newCachedThreadPool());

典型案例:短任务高并发场景(任务提交后立即被执行,无队列存储开销);

优点:性能极高;缺点:无存储,必须有消费者等待,否则生产者阻塞。

4. PriorityBlockingQueue

适用场景:需要「按优先级处理任务」的场景,比如任务调度系统、紧急任务优先处理;

典型案例:运维告警系统(严重告警优先处理);

注意:无界队列,需控制任务数量,避免 OOM。

5. DelayQueue

适用场景:「延迟任务、定时任务」场景;

典型案例:

缓存过期清理(缓存元素到期后自动移除);

消息延迟推送(比如下单后 15 分钟未支付自动取消);

定时任务调度(替代 Timer,支持多线程)。

6. LinkedTransferQueue

适用场景:生产者必须等待消费者处理完元素的场景,比如实时数据处理(数据生产后必须立即消费);

优点:性能优于 LinkedBlockingQueue,支持直接传递。

7. LinkedBlockingDeque

适用场景:需要「双端操作」的场景,比如工作窃取(Work Stealing)线程池、双向队列处理;

典型案例:ForkJoinPool 底层依赖(线程从队列两端取任务,提升并发效率)。

五、实战示例:生产者-消费者模型(基于 ArrayBlockingQueue)

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

// 有界阻塞队列,容量5

private static final BlockingQueue QUEUE = new ArrayBlockingQueue<>(5);

// 生产者线程

static class Producer implements Runnable {

@Override

public void run() {

try {

for (int i = 1; i <= 10; i++) {

String data = "数据" + i;

QUEUE.put(data); // 队列满时阻塞

System.out.println(Thread.currentThread().getName() + "生产:" + data);

Thread.sleep(500); // 模拟生产耗时

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}

// 消费者线程

static class Consumer implements Runnable {

@Override

public void run() {

try {

while (true) {

String data = QUEUE.take(); // 队列空时阻塞

System.out.println(Thread.currentThread().getName() + "消费:" + data);

Thread.sleep(1000); // 模拟消费耗时

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}

public static void main(String[] args) {

new Thread(new Producer(), "生产者1").start();

new Thread(new Consumer(), "消费者1").start();

new Thread(new Consumer(), "消费者2").start();

}

}

执行结果(核心:队列满时生产者阻塞,队列空时消费者阻塞):

生产者1生产:数据1

消费者1消费:数据1

生产者1生产:数据2

消费者2消费:数据2

生产者1生产:数据3

生产者1生产:数据4

生产者1生产:数据5

消费者1消费:数据3

生产者1生产:数据6(队列满后,生产者等待消费者消费后才继续生产)

...

六、线程池与阻塞队列的关联(核心应用)

ThreadPoolExecutor 的任务队列本质是 BlockingQueue,不同队列决定线程池的行为:

线程池类型

默认队列

队列特性

线程池行为

FixedThreadPool

LinkedBlockingQueue

无界(默认)

核心线程满后,所有任务入队,非核心线程数永远为 0(可能导致任务堆积)

CachedThreadPool

SynchronousQueue

无存储

核心线程 0,任务提交后立即创建非核心线程,超时销毁(适合短任务)

SingleThreadExecutor

LinkedBlockingQueue

无界

单线程执行,所有任务入队等待

自定义线程池(推荐)

ArrayBlockingQueue

有界

核心线程满→入队→队列满→创建非核心线程→线程数满→触发拒绝策略(可控性强)

总结

核心特性:BlockingQueue 核心是「阻塞+线程安全」,通过 ReentrantLock + Condition 实现阻塞,避免手动同步;

核心实现类:

有界高性能:ArrayBlockingQueue(单一锁)、LinkedBlockingQueue(分离锁);

直接传递:SynchronousQueue(无存储);

优先级/延迟:PriorityBlockingQueue、DelayQueue;

使用场景:

严格控容选 ArrayBlockingQueue;

高并发选 LinkedBlockingQueue(指定容量);

任务直接执行选 SynchronousQueue;

优先级/延迟任务选 PriorityBlockingQueue/DelayQueue;

核心建议:避免使用无界队列(防止 OOM),线程池优先自定义并使用有界阻塞队列。

掌握 BlockingQueue 的底层原理和使用场景,是理解 Java 并发编程(尤其是线程池、生产者-消费者)的关键,也是面试高频考点。