Java Queue 讲解

一、Queue 接口体系概览

Java 提供了一个 Queue 接口:

java 复制代码
public interface Queue<E> extends Collection<E>

常用实现如下:

复制代码
Collection
    └── Queue
            ├── Deque(双端队列)
            │       ├── ArrayDeque
            │       └── LinkedList
            ├── PriorityQueue(优先队列)
            └── BlockingQueue(并发阻塞队列)
                    ├── LinkedBlockingQueue
                    ├── ArrayBlockingQueue
                    ├── SynchronousQueue
                    └── DelayQueue

Queue 的核心方法分为两组:

操作类型 抛出异常(不推荐) 返回特殊值(推荐使用 ⭐)
入队(添加元素到队尾) add(e) offer(e)(容量满时返回 false
出队(移除并返回队首) remove() poll()(队列空时返回 null
查看(查看但不移除队首) element() peek()(队列空时返回 null

强烈建议你在日常开发中使用第二套(返回特殊值) 。因为第一套方法在遇到边界情况(比如队列空了你还要拿数据)时会直接让程序抛出异常崩溃,而第二套会温柔地返回 falsenull

二、为什么 Java 要设计这么多种队列?

队列的核心规则:先进先出 FIFO

但是实际开发中,我们会遇到不同需求:

  1. 我只是想存东西排队 → 普通队列

  2. 我希望队列满了就等待 → 阻塞队列

  3. 我希望优先处理重要任务 → 优先级队列

  4. 我希望队列容量固定 → 有界队列

  5. 我希望多线程安全地使用 → 并发队列

  6. 我希望任务延迟一段时间后才能取 → 延迟队列

  7. 我甚至不想真正存储任务,只想让两个线程直接交接 → SynchronousQueue


2-1、第一类:普通 Queue

最基础的:

java 复制代码
Queue<String> queue = new LinkedList<>();

你可以把它理解成:

一个普通的排队结构。

它本身是一个双向链表,不仅实现了 List,也完美实现了 Queue 接口。

特点:首尾插入和删除速度极快,且没有容量限制(除非内存溢出)。

例如:

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        // 面向接口编程:左边写接口 Queue,右边写实现类 LinkedList
        Queue<String> queue = new LinkedList<>();

        // 1. 入队 (offer)
        queue.offer("Alice");
        queue.offer("Bob");
        queue.offer("Charlie");
        System.out.println("当前队列: " + queue); // 输出: [Alice, Bob, Charlie]

        // 2. 查看队首 (peek)
        System.out.println("排在最前面的是: " + queue.peek()); // 输出: Alice

        // 3. 出队 (poll)
        String served = queue.poll();
        System.out.println(served + " 打完饭离开了。");
        System.out.println("剩下的队列: " + queue); // 输出: [Bob, Charlie]
    }
}

它解决什么问题?

就是:

我需要一个先进先出的数据结构。

比如:

  • 任务排队

  • 消息排队

  • BFS 广度优先搜索

  • 临时保存待处理数据

但是普通 Queue 不保证多线程安全


LinkedList:为什么它也能当 Queue?

这个地方非常重要。

LinkedList 本身不是专门为 Queue 设计的。

它其实可以同时充当:

复制代码
List
Queue
Deque

因为它底层是双向链表

所以:

java 复制代码
Queue<String> queue = new LinkedList<>();

完全可以。

但是如果你只是需要一个普通队列,现在通常更推荐:

java 复制代码
Queue<String> queue = new ArrayDeque<>();

因为 ArrayDeque 更专门针对双端队列设计。


2-2、ArrayDeque:高效双端队列(推荐!)

ArrayDeque 基于循环数组实现,是 Java 官方推荐的 Deque 首选实现。

名字拆开:

复制代码
Array + Deque

Deque = Double Ended Queue

也就是:

两头都可以进,两头都可以出。

比如:

复制代码
队头                 队尾
 ↓                     ↓
A ←→ B ←→ C ←→ D

你可以:

java 复制代码
deque.offerFirst("A");
deque.offerLast("D");

deque.pollFirst();
deque.pollLast();

所以它不仅可以当:

普通队列

复制代码
A → B → C
↑
从头取

也可以当:

复制代码
A
B
C
↑
从顶部进出

所以 ArrayDeque 一个非常重要的特点是:

既可以当 Queue,也可以当 Stack。

示例:

java 复制代码
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeDemo {
    public static void main(String[] args) {
        // 作为普通队列(FIFO)
        Deque<String> queue = new ArrayDeque<>();
        queue.offer("Task1");
        queue.offer("Task2");
        
        // 作为栈使用(LIFO)
        Deque<String> stack = new ArrayDeque<>();
        stack.push("First");
        stack.push("Second");
        System.out.println(stack.pop());  // Second(栈顶)
        
        // 双端操作
        Deque<Integer> deque = new ArrayDeque<>();
        deque.addFirst(1);  // 队首入
        deque.addLast(2);   // 队尾入
        System.out.println(deque.removeFirst()); // 1
        System.out.println(deque.removeLast());  // 2
    }
}

特点

  • ✅ 无容量限制,自动扩容

  • ✅ 比 LinkedList 更快,缓存友好

  • ✅ 内存占用更小

  • ❌ 不支持 null 元素

  • ❌ 非线程安全

最佳实践 :需要队列/栈时,优先选择 ArrayDeque,而不是 Stack 类或 LinkedList。


2-3、PriorityQueue:为什么突然叫 Priority?

普通 Queue:

复制代码
A → B → C → D

谁先进去,谁先出来。

但是有时候我们不想这样。

比如医院:

复制代码
普通病人
普通病人
危重病人
普通病人

你肯定不会:

谁先来的谁先看。

而是:

复制代码
危重病人
    ↓
普通病人
    ↓
普通病人
    ↓
普通病人

这就是:

优先级队列 Priority Queue

Java:

java 复制代码
PriorityQueue<Integer> queue = new PriorityQueue<>();

例如:

java 复制代码
queue.offer(5);
queue.offer(1);
queue.offer(3);

取出来:

复制代码
1
3
5

所以:

复制代码
普通 Queue
    ↓
先进先出

PriorityQueue
    ↓
优先级高的先出来

它已经不是严格意义上的 FIFO 了。

【示例1】:元素按优先级排序 出队,底层是小顶堆(默认自然顺序)。

java 复制代码
import java.util.PriorityQueue;
import java.util.Comparator;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 默认小顶堆(数字小的优先)
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        minHeap.offer(5);
        minHeap.offer(1);
        minHeap.offer(3);
        
        while (!minHeap.isEmpty()) {
            System.out.print(minHeap.poll() + " ");  // 1 3 5
        }
        
        // 大顶堆(自定义比较器)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(
            Comparator.reverseOrder()
        );
        
        // 对象按自定义字段排序
        PriorityQueue<Task> taskQueue = new PriorityQueue<>(
            Comparator.comparingInt(t -> t.priority)
        );
        taskQueue.offer(new Task("紧急Bug", 1));
        taskQueue.offer(new Task("写文档", 3));
        taskQueue.offer(new Task("重构代码", 2));
    }
}

class Task {
    String name;
    int priority;
    Task(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }
}

特点

  • ✅ 插入 O(log n),取队首 O(1)

  • ✅ 适合任务调度、TopK 问题

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

  • ❌ 非线程安全。


2-4、BlockingQueue:为什么叫 Blocking?

以后学习线程池时非常重要的一个。

普通 Queue:

java 复制代码
queue.poll();

如果没东西:

复制代码
队列为空
   ↓
poll()
   ↓
返回 null

但是多线程生产者消费者场景中:

复制代码
生产者
  ↓
   Queue
  ↓
消费者

如果消费者发现:

复制代码
Queue 空了

我们可能希望:

"你别返回 null,你先等着,有任务之后再干活。"

这就是:

复制代码
BlockingQueue

Blocking = 阻塞

例如:

java 复制代码
BlockingQueue<String> queue =
        new ArrayBlockingQueue<>(10);

消费者:

java 复制代码
String task = queue.take();

如果没有任务:

复制代码
Queue 空
   ↓
take()
   ↓
线程等待
   ↓
有任务了
   ↓
继续执行

这就是 BlockingQueue 的核心价值。


2-5、ArrayBlockingQueue:为什么又多了一个 Array?

因为:

复制代码
BlockingQueue
       ↓
"阻塞队列"这个概念/接口

下面可以有不同实现。

其中:

复制代码
ArrayBlockingQueue

就是:

基于数组实现的有界阻塞队列。

这里有三个关键词:

Array

底层使用数组。

Blocking

支持阻塞。

有界

创建的时候就指定容量:

java 复制代码
new ArrayBlockingQueue<>(10);

最多:

复制代码
10个元素

如果满了:

复制代码
Queue 满了
   ↓
put()
   ↓
等待

所以它特别适合:

任务数量不能无限堆积的生产者-消费者场景。


2-6、LinkedBlockingQueue------ 线程安全阻塞队列

java 复制代码
LinkedBlockingQueue

和刚才一样:

复制代码
Linked
Blocking
Queue

意思就是:

基于链表 + 支持阻塞的队列。

它和 ArrayBlockingQueue 最大的直观区别:

复制代码
ArrayBlockingQueue
        ↓
数组
        ↓
容量通常固定

LinkedBlockingQueue
        ↓
链表
        ↓
容量可以指定

例如:

java 复制代码
new LinkedBlockingQueue<>(100);

可以限制为 100。

也可以:

java 复制代码
new LinkedBlockingQueue<>();

使用默认容量上限(实际上是非常大的整数上限 Integer.MAX_VALUE)。

示例:

java 复制代码
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) {
        // 有界队列(容量100)
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
        
        // 生产者线程
        new Thread(() -> {
            try {
                queue.put("产品A");  // 队列满时阻塞等待
                System.out.println("生产: 产品A");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
        
        // 消费者线程
        new Thread(() -> {
            try {
                String product = queue.take();  // 队列空时阻塞等待
                System.out.println("消费: " + product);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

核心方法:

方法 队满/队空时的行为
put(e) / take() 阻塞等待
offer(e, time, unit) / poll(time, unit) 超时等待
offer(e) / poll() 立即返回 false/null

【注意】:VS ArrayBlockingQueue

Array 和 Linked 指的是这个"队列内部怎么存数据",也就是底层数据结构,不是你往队列里面放的数据类型。

所以 ArrayBlockingQueue 和 LinkedBlockingQueue 都可以存 StringInteger、对象、任务等等。

虽然功能很相似 ,但底层实现不同,会导致一些性能和使用特征上的差异

最核心的区别可以先这么理解:

ArrayBlockingQueue LinkedBlockingQueue
底层 数组 链表
是否阻塞
是否线程安全
是否 FIFO
是否可以存对象
容量 必须指定 可以指定,也有默认容量
内存 相对紧凑 每个节点需要额外对象空间
适合 容量明确、边界清晰 任务量可能波动的场景

2-7、DelayQueue:为什么叫 Delay?

普通队列:

复制代码
任务来了
 ↓
马上可以取

但是有些任务:

必须等到某个时间才能取。

比如:

复制代码
短信验证码
 ↓
60秒后过期

或者:

复制代码
订单
 ↓
30分钟后检查是否支付

这时候可以用:

复制代码
DelayQueue

它的特点:

任务没有到期之前,不能被取出来。

比如:

复制代码
任务A:10:00执行
任务B:10:05执行
任务C:10:10执行

即使任务 C 先进入队列:

复制代码
C
B
A

也不是说谁先进入谁先执行。

而是:

复制代码
到期时间最早
      ↓
     A
      ↓
     B
      ↓
     C

所以:

复制代码
DelayQueue
     ↓
"时间到了才能取"

核心特点:

特性 说明
元素要求 元素必须实现 Delayed 接口
出队规则 只有到期(delay ≤ 0) 的元素才能被 take() 取出
队首 永远是最早到期的元素
容量 无界队列,理论上无限大
线程安全 线程安全,支持阻塞等待

2-8、SynchronousQueue:这个名字特别容易懵

它和普通队列最大的区别是:

它几乎不存东西。

你可以把它理解成:

复制代码
生产者 ─────────→ 消费者

不是:

复制代码
生产者
   ↓
┌───────────┐
│   Queue   │
│ A B C D   │
└───────────┘
   ↓
消费者

而是:

复制代码
生产者
   ↓
[A] ←→ 消费者

生产者:

java 复制代码
queue.put("A");

必须等到有消费者:

java 复制代码
queue.take();

两边才能完成交接。

所以叫:

Synchronous:同步交接

它非常适合:

任务不需要在队列里堆积,生产者和消费者直接交接。


三、把这些队列放到一张图里

复制代码
                         Queue
                           │
             ┌─────────────┼─────────────┐
             │             │             │
          普通队列       优先级队列      阻塞队列
             │             │             │
        ArrayDeque     PriorityQueue      │
        LinkedList                         │
                                ┌──────────┼───────────┐
                                │          │           │
                       ArrayBlockingQueue  │     SynchronousQueue
                                           │
                                  LinkedBlockingQueue
                                           │
                                      DelayQueue

不过这里有一个很重要的概念纠正

ArrayDequePriorityQueueBlockingQueue 并不是简单的"父子分类关系"。更准确地说,是不同接口/实现针对不同需求组合出来的设计


四、小结

① 我要普通 FIFO?

复制代码
ArrayDeque

② 我要两头都能操作?

复制代码
Deque
 ↓
ArrayDeque

③ 我要按照优先级取?

复制代码
PriorityQueue

④ 我要多线程 + 队列空了就等待?

复制代码
BlockingQueue

⑤ 我要固定容量?

复制代码
ArrayBlockingQueue

⑥ 我要链表实现的阻塞队列?

复制代码
LinkedBlockingQueue

⑦ 我要"到时间才能取"?

复制代码
DelayQueue

⑧ 我要生产者和消费者直接交接,不在队列里存任务?

复制代码
SynchronousQueue
相关推荐
Java成神之路-2 天前
深入理解 HashMap 扩容时机:JDK 7 vs JDK 8 的设计差异与取舍
java·juc
ruleslol4 天前
AtomicIntegerArray 的常用方法
juc
LayZhangStrive1 个月前
JUC相关的函数、注解、变量杂记
java·面试·多线程·juc
程序员二叉2 个月前
【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案
java·开发语言·面试·职场和发展·juc
程序员二叉2 个月前
【JUC】线程池全套深度详解|参数|流程|拒绝策略|调优|异常处理
java·开发语言·jvm·算法·面试·juc
程序员二叉2 个月前
【JUC】AQS底层深度拆解|独占/共享模式|队列原理全详解
java·开发语言·面试·juc
fengxin_rou2 个月前
Java并发手撕题详解:原理、实现与面试避坑指南
多线程·cas·juc·lru
fengxin_rou3 个月前
【从零开始的JUC并发第四章】:JUC常用工具类
并发·juc