Java中的BlockingDeque是一个支持双端插入和移除操作的阻塞队列接口,它继承了BlockingQueue接口,既具备阻塞队列的特性,又允许从队列的头部和尾部两个方向进行元素的添加和获取操作,非常适合需要两端处理数据的多线程场景。

BlockingDeque的核心特性
BlockingDeque的核心特性主要体现在两个方面,一方面是双端操作能力,另一方面是阻塞特性。双端操作意味着我们可以在队列的头部添加元素、获取元素,也可以在队列的尾部添加元素、获取元素,相比普通的BlockingQueue只能从尾部添加、头部获取,使用场景更加灵活。
阻塞特性则体现在当队列满时,向队列添加元素的操作会被阻塞,直到队列有空闲空间;当队列为空时,从队列获取元素的操作会被阻塞,直到队列中有新的元素加入,这一特性可以有效解决多线程间的数据传递和线程协作问题,避免忙等带来的性能损耗。
常用实现类LinkedBlockingDeque
Java中BlockingDeque的常用实现类是LinkedBlockingDeque,它是基于链表结构实现的双端阻塞队列,默认情况下队列的容量为Integer的最大值,我们也可以手动指定队列的容量,当队列达到指定容量后,添加操作就会触发阻塞。
和基于数组的ArrayBlockingDeque相比,LinkedBlockingDeque的吞吐量通常更高,因为它的入队和出队操作使用的是不同的锁,而ArrayBlockingDeque使用的是同一把锁,在高并发场景下LinkedBlockingDeque的性能表现更好。
核心方法说明
BlockingDeque提供了一系列用于双端操作的方法,我们可以将这些方法分为四类,不同类别的方法在队列满或空时的处理方式不同:
| 方法类型 | 头部操作(获取/移除) | 尾部操作(添加) | 队列满/空时的处理方式 |
|---|---|---|---|
| 抛出异常 | removeFirst() | addLast(E e) | 队列空时移除抛出NoSuchElementException,队列满时添加抛出IllegalStateException |
| 返回特殊值 | pollFirst() | offerLast(E e) | 队列空时返回null,队列满时添加返回false |
| 阻塞等待 | takeFirst() | putLast(E e) | 队列空时一直阻塞,队列满时一直阻塞直到有空闲空间 |
| 超时等待 | pollFirst(long timeout, TimeUnit unit) | offerLast(E e, long timeout, TimeUnit unit) | 队列空/满时阻塞指定时间,超时后返回null或false |
对应的头部添加和尾部获取方法分别为addFirst、offerFirst、putFirst、pollFirst(超时版本)、removeLast、pollLast、takeLast、offerLast(超时版本),用法和上述对应类型的方法一致。
实现双端阻塞队列示例
下面我们通过一个完整的示例来演示如何利用LinkedBlockingDeque实现双端阻塞队列,示例中模拟两个生产者线程分别从队列的头部和尾部添加元素,两个消费者线程分别从队列的头部和尾部获取元素,展示双端操作的特性。
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
public class BlockingDequeDemo {
// 创建容量为5的双端阻塞队列
private static final BlockingDeque<String> deque = new LinkedBlockingDeque<>(5);
// 头部生产者,向队列头部添加元素
static class HeadProducer implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
String element = "头部元素-" + i;
// 向队列头部添加元素,队列满则阻塞
deque.putFirst(element);
System.out.println(Thread.currentThread().getName() + " 向头部添加元素:" + element);
TimeUnit.MILLISECONDS.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 尾部生产者,向队列尾部添加元素
static class TailProducer implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
String element = "尾部元素-" + i;
// 向队列尾部添加元素,队列满则阻塞
deque.putLast(element);
System.out.println(Thread.currentThread().getName() + " 向尾部添加元素:" + element);
TimeUnit.MILLISECONDS.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 头部消费者,从队列头部获取元素
static class HeadConsumer implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
// 从队列头部获取元素,队列空则阻塞
String element = deque.takeFirst();
System.out.println(Thread.currentThread().getName() + " 从头部获取元素:" + element);
TimeUnit.MILLISECONDS.sleep(300);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 尾部消费者,从队列尾部获取元素
static class TailConsumer implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
// 从队列尾部获取元素,队列空则阻塞
String element = deque.takeLast();
System.out.println(Thread.currentThread().getName() + " 从尾部获取元素:" + element);
TimeUnit.MILLISECONDS.sleep(300);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// 启动两个生产者线程和两个消费者线程
new Thread(new HeadProducer(), "头部生产者").start();
new Thread(new TailProducer(), "尾部生产者").start();
new Thread(new HeadConsumer(), "头部消费者").start();
new Thread(new TailConsumer(), "尾部消费者").start();
}
}
运行上述代码,我们可以看到元素会被分别从队列的头部和尾部添加和获取,当队列满时添加操作会阻塞,当队列空时获取操作会阻塞,完全符合双端阻塞队列的特性。
使用注意事项
在使用BlockingDeque实现双端阻塞队列时,需要注意以下几点。首先,如果需要限制队列容量,一定要手动指定LinkedBlockingDeque的容量,否则默认容量为Integer最大值,可能会导致内存溢出问题。
其次,选择方法时要根据业务场景决定,如果需要必须成功添加或获取元素,就使用阻塞的put和take系列方法;如果允许添加或获取失败,就可以使用返回特殊值的offer和poll系列方法,避免线程长时间阻塞。
最后,双端阻塞队列虽然灵活,但也要合理设计元素的添加和获取方向,避免逻辑混乱,比如在不需要双端操作的场景下,优先使用普通的BlockingQueue即可,不需要过度设计。
BlockingDeque双端阻塞队列Java多线程LinkedBlockingDeque修改时间:2026-07-08 22:27:14