Java中单向队列的多种实现类详解
Java中单向队列多种实现类详解:单线程与多线程全景式解析
- 前言
- 一、队列的基本接口
- 二、单线程队列实现类
- 2.1 LinkedList
- 2.1.1 简介
- 2.1.2 常用方法
- 2.1.3 优缺点
- 2.1.4 适用范围
- 2.2 ArrayDeque
- 2.2.1 简介
- 2.2.2 常用方法
- 2.2.3 优缺点
- 2.2.4 适用范围
- 2.3 PriorityQueue
- 2.3.1 简介
- 2.3.2 常用方法
- 2.3.3 优缺点
- 2.3.4 适用范围
- 三、多线程并发队列实现类
- 3.1 ConcurrentLinkedQueue
- 3.1.1 简介
- 3.1.2 常用方法
- 3.1.3 优缺点
- 3.1.4 适用范围
- 3.2 ArrayBlockingQueue
- 3.2.1 简介
- 3.2.2 常用方法
- 3.2.3 优缺点
- 3.2.4 适用范围
- 3.3 LinkedBlockingQueue
- 3.3.1 简介
- 3.3.2 常用方法
- 3.3.3 优缺点
- 3.3.4 适用范围
- 3.4 PriorityBlockingQueue
- 3.4.1 简介
- 3.4.2 常用方法
- 3.4.3 优缺点
- 3.4.4 适用范围
- 3.5 DelayQueue
- 3.5.1 简介
- 3.5.2 常用方法
- 3.5.3 优缺点
- 3.5.4 适用范围
- 3.6 SynchronousQueue
- 3.6.1 简介
- 3.6.2 常用方法
- 3.6.3 优缺点
- 3.6.4 适用范围
- 四、队列实现类对比总结
- 五、最佳实践与选择建议
前言
队列(Queue)作为一种常用的数据结构,在Java开发中,无论日常业务开发还是算法题、并发编程,队列都有着广泛应用。Java标准库为我们提供了丰富的队列实现类,既有适合单线程场景的高性能实现,也有专为多线程并发设计的线程安全队列。本文我将系统梳理Java中单向队列的实现类,详细分析它们的用法、方法、优缺点及适用场景,帮你在实际开发中游刃有余地选择合适的队列。如果想了解双端队列的实现类和用法,请进我主页搜索查看下一篇双端队列的实现类和用法专题。
一、队列的基本接口
Java中队列的核心接口是java.util.Queue
,它定义了队列的基本操作:
offer(E e)
:入队,添加元素,返回是否成功。poll()
:出队,移除并返回队首元素,队列为空时返回null
。peek()
:查看队首元素但不移除,队列为空时返回null
。
此外,Deque
(双端队列)接口扩展了队列的功能,支持两端插入和删除。
二、单线程队列实现类
2.1 LinkedList
2.1.1 简介
LinkedList
实现了Queue
和Deque
接口,底层是双向链表。它既可以作为队列,也可以作为栈和双端队列使用。
2.1.2 常用方法
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); // 入队
queue.offer(2);
System.out.println(queue.poll()); // 1,出队
System.out.println(queue.peek()); // 2,查看队首
System.out.println(queue.isEmpty()); // false
2.1.3 优缺点
- 优点:
- 支持队列、栈、双端队列等多种用法,灵活性高。
- 插入和删除操作效率高(O(1))。
- 缺点:
- 不是线程安全的。
- 每个节点有额外的指针开销,内存占用较大。
- 随机访问性能差(O(n))。
2.1.4 适用范围
- 适合单线程环境下,频繁插入和删除的场景。
- 需要双端队列或栈功能时也可选用。
2.2 ArrayDeque
2.2.1 简介
ArrayDeque
实现了Queue
和Deque
接口,底层是循环数组。它是Java官方推荐的队列和栈实现。
2.2.2 常用方法
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 1
System.out.println(queue.peek()); // 2
System.out.println(queue.isEmpty()); // false
2.2.3 优缺点
- 优点:
- 基于数组,内存利用率高,性能优于
LinkedList
。 - 支持队列和栈操作,API丰富。
- 没有同步开销,单线程下性能最好。
- 基于数组,内存利用率高,性能优于
- 缺点:
- 不是线程安全的。
- 不能存储
null
元素。 - 扩容时有一定性能开销。
2.2.4 适用范围
- 单线程环境下,推荐作为队列或栈的实现。
- 需要高性能、低内存开销的场景。
2.3 PriorityQueue
2.3.1 简介
PriorityQueue
实现了Queue
接口,底层是最小堆(优先队列)。元素出队顺序由优先级决定。
2.3.2 常用方法
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(3);
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 1
System.out.println(queue.peek()); // 2
2.3.3 优缺点
- 优点:
- 支持优先级队列,元素自动排序。
- 插入和删除的时间复杂度为O(log n)。
- 缺点:
- 不是线程安全的。
- 不能存储
null
元素。 - 只保证队首元素有序,遍历时无序。
2.3.4 适用范围
- 需要优先级调度、任务排序等场景。
- 单线程环境下的优先级队列。
三、多线程并发队列实现类
3.1 ConcurrentLinkedQueue
3.1.1 简介
ConcurrentLinkedQueue
是基于链表的无界线程安全队列,采用CAS算法实现高并发。
3.1.2 常用方法
Queue<Integer> queue = new ConcurrentLinkedQueue<>();
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 1
System.out.println(queue.peek()); // 2
3.1.3 优缺点
- 优点:
- 线程安全,无需加锁,适合高并发场景。
- 无界队列,容量只受内存限制。
- 缺点:
- 不支持阻塞操作。
- 不能存储
null
元素。
3.1.4 适用范围
- 多线程环境下的高并发队列。
- 适合任务分发、消息队列等场景。
3.2 ArrayBlockingQueue
3.2.1 简介
ArrayBlockingQueue
是基于数组的有界阻塞队列,支持阻塞的入队和出队操作。
3.2.2 常用方法
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);
queue.put(1); // 阻塞式入队
queue.put(2);
System.out.println(queue.take()); // 1,阻塞式出队
System.out.println(queue.peek()); // 2
3.2.3 优缺点
- 优点:
- 线程安全,支持阻塞操作。
- 有界队列,防止内存溢出。
- 缺点:
- 容量固定,需提前指定。
- 扩容不方便。
3.2.4 适用范围
- 生产者-消费者模式。
- 需要容量限制的多线程队列。
3.3 LinkedBlockingQueue
3.3.1 简介
LinkedBlockingQueue
是基于链表的可选有界阻塞队列,默认容量为Integer.MAX_VALUE
。
3.3.2 常用方法
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
queue.put(1);
queue.put(2);
System.out.println(queue.take()); // 1
System.out.println(queue.peek()); // 2
3.3.3 优缺点
- 优点:
- 线程安全,支持阻塞操作。
- 可选有界,灵活性高。
- 插入和删除操作效率高。
- 缺点:
- 内存占用比数组实现高。
- 默认无界,需注意内存溢出风险。
3.3.4 适用范围
- 生产者-消费者模式。
- 任务调度、线程池等场景。
3.4 PriorityBlockingQueue
3.4.1 简介
PriorityBlockingQueue
是支持优先级的线程安全无界阻塞队列。
3.4.2 常用方法
BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
queue.put(3);
queue.put(1);
queue.put(2);
System.out.println(queue.take()); // 1
System.out.println(queue.peek()); // 2
3.4.3 优缺点
- 优点:
- 线程安全,支持优先级队列。
- 无界队列,容量只受内存限制。
- 缺点:
- 不保证元素的遍历顺序。
- 不能存储
null
元素。
3.4.4 适用范围
- 多线程环境下的优先级任务调度。
- 定时任务、调度中心等场景。
3.5 DelayQueue
3.5.1 简介
DelayQueue
是支持延迟出队的线程安全无界阻塞队列,元素只有到期后才能被取出。
3.5.2 常用方法
class MyTask implements Delayed {private long time;public MyTask(long delay) { this.time = System.currentTimeMillis() + delay; }public long getDelay(TimeUnit unit) { return unit.convert(time - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }public int compareTo(Delayed o) { return Long.compare(this.time, ((MyTask)o).time); }
}DelayQueue<MyTask> queue = new DelayQueue<>();
queue.put(new MyTask(1000)); // 1秒后可取出
System.out.println(queue.take()); // 阻塞直到到期
3.5.3 优缺点
- 优点:
- 线程安全,支持延迟队列。
- 适合定时任务、缓存等场景。
- 缺点:
- 只能存储实现
Delayed
接口的元素。 - 无界队列,需注意内存溢出。
- 只能存储实现
3.5.4 适用范围
- 定时任务调度、延迟消息等场景。
3.6 SynchronousQueue
3.6.1 简介
SynchronousQueue
是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作。
3.6.2 常用方法
BlockingQueue<Integer> queue = new SynchronousQueue<>();
new Thread(() -> {try { queue.put(1); } catch (InterruptedException e) {}
}).start();
System.out.println(queue.take()); // 1
3.6.3 优缺点
- 优点:
- 线程安全,适合线程间直接传递数据。
- 缺点:
- 不存储元素,不能缓存数据。
3.6.4 适用范围
- 线程池、任务直接交付等场景。
四、队列实现类对比总结
类名 | 线程安全 | 是否阻塞 | 是否有界 | 是否支持优先级 | 适用场景 |
---|---|---|---|---|---|
LinkedList | 否 | 否 | 否 | 否 | 单线程队列/栈/双端队列 |
ArrayDeque | 否 | 否 | 否 | 否 | 单线程高性能队列/栈 |
PriorityQueue | 否 | 否 | 否 | 是 | 单线程优先级队列 |
ConcurrentLinkedQueue | 是 | 否 | 否 | 否 | 高并发无界队列 |
ArrayBlockingQueue | 是 | 是 | 是 | 否 | 生产者-消费者,有界队列 |
LinkedBlockingQueue | 是 | 是 | 可选 | 否 | 生产者-消费者,任务调度 |
PriorityBlockingQueue | 是 | 是 | 否 | 是 | 并发优先级队列 |
DelayQueue | 是 | 是 | 否 | 否 | 定时/延迟任务 |
SynchronousQueue | 是 | 是 | 是 | 否 | 线程间直接传递 |
五、最佳实践与选择建议
- 单线程场景:优先选择
ArrayDeque
,如需双端操作可用LinkedList
。 - 需要优先级:单线程用
PriorityQueue
,多线程用PriorityBlockingQueue
。 - 高并发场景:用
ConcurrentLinkedQueue
(无阻塞),或BlockingQueue
系列(阻塞)。 - 生产者-消费者:推荐
ArrayBlockingQueue
、LinkedBlockingQueue
。 - 定时/延迟任务:用
DelayQueue
。 - 线程间直接传递:用
SynchronousQueue
。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ