Java中的Queue是集合框架里的重要接口,继承自Collection接口,主要用来存储待处理的有序元素,遵循先进先出或者特定优先级顺序的规则,不同实现类还支持阻塞、非阻塞等不同特性,适配多种开发需求。

Queue的核心结构特性
Queue的设计目标是为了提供统一的队列操作规范,核心特性可以分为基础特性和实现类扩展特性两部分。
基础操作特性
Queue定义了队列的标准操作方法,避免直接使用集合的通用方法导致逻辑混乱,核心操作分为两类:
- 添加元素:
add(E e)方法在队列满时抛出异常,offer(E e)方法在队列满时返回false,更适合有容量限制的队列。 - 获取/移除元素:
remove()方法在队列空时抛出异常,poll()方法在队列空时返回null;element()方法获取队首元素但不移除,队列空时抛异常,peek()方法队列空时返回null。
常见实现类特性
Queue有不同的实现类,适配不同的场景需求,常见实现类的特性如下:
| 实现类 | 核心特性 | 是否线程安全 |
|---|---|---|
| LinkedList | 基于链表实现,非阻塞,无容量限制,支持先进先出 | 否 |
| ArrayDeque | 基于数组实现,非阻塞,可当作栈或队列使用,效率更高 | 否 |
| PriorityQueue | 基于堆实现,按照元素优先级排序,而非插入顺序 | 否 |
| ArrayBlockingQueue | 基于数组的有界阻塞队列,支持公平锁配置 | 是 |
| LinkedBlockingQueue | 基于链表的可选有界阻塞队列,吞吐量通常更高 | 是 |
| PriorityBlockingQueue | 支持优先级的阻塞队列,无容量上限 | 是 |
Queue适用的功能场景
生产者消费者模型
生产者消费者模型是Queue最典型的应用场景,生产者线程负责生成数据放入队列,消费者线程从队列中取出数据进行处理,队列作为中间的缓冲容器,解耦生产者和消费者的速度差异。
如果是单线程场景或者不需要线程安全的场景,可以使用LinkedList或者ArrayDeque作为队列容器,示例代码如下:
import java.util.ArrayDeque;
import java.util.Queue;
public class SimpleProducerConsumer {
// 定义队列作为缓冲容器
private static final Queue<String> TASK_QUEUE = new ArrayDeque<>();
// 队列最大容量
private static final int MAX_SIZE = 5;
// 生产者方法
public static void produce(String task) {
if (TASK_QUEUE.size() < MAX_SIZE) {
TASK_QUEUE.offer(task);
System.out.println("生产任务:" + task + ",当前队列大小:" + TASK_QUEUE.size());
} else {
System.out.println("队列已满,无法生产任务:" + task);
}
}
// 消费者方法
public static void consume() {
String task = TASK_QUEUE.poll();
if (task != null) {
System.out.println("消费任务:" + task + ",当前队列大小:" + TASK_QUEUE.size());
} else {
System.out.println("队列为空,无任务可消费");
}
}
public static void main(String[] args) {
// 模拟生产任务
for (int i = 0; i < 7; i++) {
produce("任务" + i);
}
// 模拟消费任务
for (int i = 0; i < 7; i++) {
consume();
}
}
}
如果是多线程场景,需要使用线程安全的阻塞队列,比如ArrayBlockingQueue,阻塞特性可以让生产者在队列满时自动阻塞,消费者在队列空时自动阻塞,不需要手动编写等待逻辑,示例代码如下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingProducerConsumer {
// 定义有界阻塞队列,容量为5
private static final BlockingQueue<String> TASK_QUEUE = new ArrayBlockingQueue<>(5);
// 生产者线程
static class Producer implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
String task = "任务" + i;
// 队列满时会自动阻塞等待
TASK_QUEUE.put(task);
System.out.println(Thread.currentThread().getName() + "生产任务:" + task + ",当前队列大小:" + TASK_QUEUE.size());
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者线程
static class Consumer implements Runnable {
@Override
public void run() {
try {
while (true) {
// 队列空时会自动阻塞等待
String task = TASK_QUEUE.take();
System.out.println(Thread.currentThread().getName() + "消费任务:" + task + ",当前队列大小:" + TASK_QUEUE.size());
Thread.sleep(300);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// 启动一个生产者线程和两个消费者线程
new Thread(new Producer(), "生产者线程").start();
new Thread(new Consumer(), "消费者线程1").start();
new Thread(new Consumer(), "消费者线程2").start();
}
}
线程池任务调度
Java的线程池内部就是使用Queue来存储待执行的任务,当线程池中的核心线程都在忙碌时,新提交的任务会被放入队列中等待执行。
不同类型的线程池会使用不同的队列实现:比如固定大小的线程池通常使用LinkedBlockingQueue作为任务队列,而单线程线程池也使用LinkedBlockingQueue;定时线程池使用DelayedWorkQueue,支持按延迟时间执行任务。
如果我们需要自定义线程池,也可以根据需求选择合适的队列,比如需要限制任务队列的最大容量,避免任务堆积过多导致内存溢出,就可以选择ArrayBlockingQueue,示例代码如下:
import java.util.concurrent.*;
public class CustomThreadPool {
public static void main(String[] args) {
// 自定义线程池:核心线程数2,最大线程数4,队列容量3
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
4,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
// 提交6个任务
for (int i = 0; i < 6; i++) {
int taskId = i;
try {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务:" + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} catch (RejectedExecutionException e) {
System.out.println("任务" + taskId + "被拒绝执行,队列已满且线程数达到最大值");
}
}
executor.shutdown();
}
}
消息/数据缓冲
在需要临时存储一批待处理数据的场景中,Queue可以作为缓冲容器,比如在IO操作的批量处理场景中,我们可以将读取到的数据先放入Queue,再批量写入磁盘或者发送到其他服务,减少IO操作的次数,提升性能。
比如读取文件内容后临时存储,再批量处理,使用ArrayDeque作为缓冲的示例如下:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;
public class DataBufferExample {
public static void main(String[] args) {
// 缓冲队列,存储读取到的行数据,满10条批量处理
Queue<String> lineBuffer = new ArrayDeque<>(10);
String filePath = "test.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
lineBuffer.offer(line);
// 队列满10条时批量处理
if (lineBuffer.size() == 10) {
processBatch(lineBuffer);
lineBuffer.clear();
}
}
// 处理剩余不足10条的数据
if (!lineBuffer.isEmpty()) {
processBatch(lineBuffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 批量处理数据的方法
private static void processBatch(Queue<String> lines) {
System.out.println("批量处理" + lines.size() + "条数据:");
for (String line : lines) {
// 模拟处理逻辑
System.out.println("处理数据:" + line);
}
}
}
优先级任务处理
如果任务有优先级区分,需要高优先级的任务先被处理,就可以使用PriorityQueue或者PriorityBlockingQueue,这两个队列会根据元素的优先级排序,而不是按照插入顺序处理。
使用PriorityQueue处理优先级任务的示例如下,我们定义任务对象带优先级属性,队列会按照优先级从高到低处理:
import java.util.PriorityQueue;
public class PriorityTaskExample {
// 定义任务类,实现Comparable接口定义优先级规则
static class Task implements Comparable<Task> {
private String name;
private int priority; // 优先级,数字越小优先级越高
public Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public int compareTo(Task other) {
// 按照优先级升序排列,优先级高的先出队
return Integer.compare(this.priority, other.priority);
}
@Override
public String toString() {
return "Task{name='" + name + "', priority=" + priority + "}";
}
}
public static void main(String[] args) {
// 创建优先级队列
PriorityQueue<Task> priorityQueue = new PriorityQueue<>();
// 添加不同优先级的任务
priorityQueue.offer(new Task("低优先级任务", 3));
priorityQueue.offer(new Task("高优先级任务", 1));
priorityQueue.offer(new Task("中优先级任务", 2));
// 依次取出任务,会按照优先级顺序输出
while (!priorityQueue.isEmpty()) {
System.out.println("处理任务:" + priorityQueue.poll());
}
}
}
Queue使用注意事项
在使用Queue的时候,需要根据场景选择合适的实现类:如果是单线程场景,优先选择ArrayDeque,效率比LinkedList更高;如果是多线程场景,必须选择线程安全的阻塞队列实现类;如果需要优先级排序,选择PriorityQueue或者PriorityBlockingQueue。
另外要注意队列的容量设置,有界队列可以避免任务无限堆积导致内存溢出,无界队列在任务生产速度远大于消费速度时可能存在风险,需要根据实际业务情况选择。
Queue的核心价值是提供规范的有序元素存储和获取能力,解耦生产者和消费者,合理选择实现类可以大幅提升代码的健壮性和可维护性。