【Java从入门到入土】24:Queue队列:从排队到优先处理

Queue(队列)是Java集合框架中处理“有序序列”的核心容器,它的设计哲学是“按规则存取元素”——从最基础的FIFO(先进先出)队列,到支持优先级的PriorityQueue,再到线程安全的阻塞队列,队列几乎贯穿了Java并发编程的所有场景(如线程池、消息队列)。新手常问:“队列和List有什么区别?阻塞队列为什么能支撑线程池?PriorityQueue的排序原理是什么?” 今天从队列基础、核心实现、并发队列、实战应用四个维度,把队列的“排队逻辑”讲透,让你理解从“普通排队”到“优先处理”的完整实现。

📚 Queue基础:FIFO与优先级队列

Queue是一个接口,定义了“队列”的核心行为,它的实现类分为两大方向:普通队列(FIFO)优先级队列(按优先级排序),前者遵循“先来先服务”,后者打破顺序,优先处理高优先级元素。

1. Queue接口核心方法

Queue定义了两套操作方法(抛异常 vs 返回特殊值),适配不同的错误处理场景:

操作 抛异常(失败时) 返回特殊值(失败时) 说明
添加元素到队尾 add(e) offer(e) 队列满时,add抛IllegalStateException,offer返回false
取出队首元素 remove() poll() 队列空时,remove抛NoSuchElementException,poll返回null
查看队首元素 element() peek() 队列空时,element抛NoSuchElementException,peek返回null

基础用法示例

import java.util.LinkedList;
import java.util.Queue;

public class QueueBasicTest {
    public static void main(String[] args) {
        // LinkedList实现了Queue接口,是最常用的非阻塞FIFO队列
        Queue<String> queue = new LinkedList<>();

        // 1. 添加元素(offer更安全,推荐)
        queue.offer("任务1");
        queue.offer("任务2");
        queue.offer("任务3");
        System.out.println("队列初始状态:" + queue); // [任务1, 任务2, 任务3]

        // 2. 查看队首(不取出)
        System.out.println("队首元素:" + queue.peek()); // 任务1

        // 3. 取出队首(FIFO,先进先出)
        while (!queue.isEmpty()) {
            String task = queue.poll();
            System.out.println("处理任务:" + task); // 依次输出:任务1→任务2→任务3
        }

        // 4. 空队列poll返回null(安全)
        System.out.println("空队列poll:" + queue.poll()); // null
    }
}

2. 普通队列 vs 优先级队列

类型 核心规则 典型实现类 适用场景
普通队列(FIFO) 先进先出,按插入顺序处理 LinkedList、ArrayDeque 普通任务排队、消息转发
优先级队列 按优先级排序,高优先级先处理 PriorityQueue 任务调度、事件优先级处理

🚦 阻塞队列:ArrayBlockingQueue与LinkedBlockingQueue

阻塞队列(BlockingQueue)是并发编程的核心工具,它在普通队列基础上增加了“阻塞”特性:

  • 入队阻塞:队列满时,入队线程阻塞,直到队列有空位;
  • 出队阻塞:队列空时,出队线程阻塞,直到队列有元素。

阻塞队列是线程池、生产者-消费者模型的核心依赖,JDK提供了多个实现,其中ArrayBlockingQueueLinkedBlockingQueue是最常用的两个。

1. 核心阻塞方法

BlockingQueue扩展了Queue接口,新增4个核心阻塞方法:

操作 阻塞版本(等待) 超时版本(等待指定时间)
入队 put(e) offer(e, timeout, unit)
出队 take() poll(timeout, unit)

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

(1)核心特性
  • 底层:定长数组,创建时必须指定容量(有界);
  • 锁机制:单锁(ReentrantLock),入队/出队共用一把锁,并发度较低;
  • 公平性:支持公平/非公平锁(公平锁按线程等待顺序访问,性能略低);
  • 适用场景:固定容量、数据量可预估的场景(如固定大小的消息队列)。
(2)用法示例(生产者-消费者)
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ArrayBlockingQueueTest {
    // 容量为5的有界阻塞队列
    private static final BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

    public static void main(String[] args) {
        // 生产者线程:生产消息
        new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    String msg = "消息" + i;
                    queue.put(msg); // 队列满时阻塞
                    System.out.println("生产者生产:" + msg + ",队列大小:" + queue.size());
                    Thread.sleep(100); // 模拟生产耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "生产者").start();

        // 消费者线程:消费消息
        new Thread(() -> {
            try {
                while (true) {
                    String msg = queue.take(); // 队列空时阻塞
                    System.out.println("消费者消费:" + msg + ",队列大小:" + queue.size());
                    Thread.sleep(500); // 模拟消费耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "消费者").start();
    }
}

3. LinkedBlockingQueue:链表实现的阻塞队列

(1)核心特性
  • 底层:双向链表,默认容量Integer.MAX_VALUE(可认为无界,也可指定容量);
  • 锁机制:双锁(ReentrantLock),入队锁和出队锁分离,并发度更高;
  • 适用场景:高并发、数据量不可预估的场景(如线程池的任务队列)。
(2)核心对比(ArrayBlockingQueue vs LinkedBlockingQueue)
特性 ArrayBlockingQueue LinkedBlockingQueue
底层结构 定长数组 双向链表
容量 必须指定(有界) 可选(默认无界)
锁机制 单锁(入队/出队互斥) 双锁(入队/出队并行)
内存占用 连续内存,无冗余 非连续内存,节点有额外开销
并发性能 较低 较高
适用场景 固定容量、低并发 动态容量、高并发

4. 阻塞队列核心应用:生产者-消费者模型

阻塞队列完美解决了生产者和消费者的“同步问题”——无需手动加锁/等待,底层通过Condition实现阻塞/唤醒:

渲染错误: Mermaid 渲染失败: Parse error on line 2: ... B[BlockingQueue.put(e)] B --> C{队列满 -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

🔄 双端队列Deque:栈与队列的结合体

Deque(Double Ended Queue)是“双端队列”,支持在队首和队尾同时进行入队/出队操作,既能当FIFO队列用,也能当LIFO栈用,是比Queue更灵活的容器。

1. Deque核心方法

Deque整合了队列和栈的操作,核心方法分类如下:

操作方向 队列(FIFO) 栈(LIFO)
入队/入栈 offerLast(e) / addLast(e) offerFirst(e) / push(e)
出队/出栈 pollFirst() / removeFirst() pollLast() / pop()
查看首/顶 peekFirst() / getFirst() peekLast() / peek()

2. 核心实现类:ArrayDeque

ArrayDeque是Deque的首选实现类,底层是可扩容的循环数组,性能优于LinkedList(栈/队列场景):

  • 无容量限制(自动扩容),非线程安全;
  • 循环数组避免了普通数组的元素移动,首尾操作O(1);
  • 不允许存储null元素。
用法示例(栈+队列)
import java.util.ArrayDeque;
import java.util.Deque;

public class DequeTest {
    public static void main(String[] args) {
        Deque<String> deque = new ArrayDeque<>();

        // 1. 作为FIFO队列使用
        deque.offerLast("队列元素1");
        deque.offerLast("队列元素2");
        System.out.println("队列出队:" + deque.pollFirst()); // 队列元素1

        // 2. 作为LIFO栈使用(替代Stack,Stack是老旧类)
        deque.push("栈元素1");
        deque.push("栈元素2");
        System.out.println("栈出栈:" + deque.pop()); // 栈元素2

        // 3. 双端操作
        deque.offerFirst("队首元素");
        deque.offerLast("队尾元素");
        System.out.println("队首取出:" + deque.pollFirst()); // 队首元素
        System.out.println("队尾取出:" + deque.pollLast()); // 队尾元素
    }
}

3. Deque vs Stack

Java中的Stack类是老旧实现(继承Vector),性能低且线程安全(无意义),官方推荐用Deque替代Stack

特性 Stack(不推荐) Deque(ArrayDeque)
底层结构 数组(Vector子类) 循环数组
线程安全 是(synchronized)
性能
支持操作 仅栈操作 栈+队列+双端操作
空值 允许 不允许

🌿 优先级队列PriorityQueue:堆的实现

PriorityQueue是基于二叉堆实现的优先级队列,核心特点是“元素按优先级排序,而非插入顺序”——每次取出的都是优先级最高的元素(堆顶)。

1. 底层原理:二叉堆

二叉堆是完全二叉树的一种,分为“最小堆”和“最大堆”,PriorityQueue默认是最小堆(小值优先):

  • 最小堆:每个父节点的值 ≤ 子节点的值,堆顶是最小值;
  • 堆的操作(插入/删除)时间复杂度O(logn),查看堆顶O(1);
  • 底层用数组存储堆结构,通过索引计算父子节点位置:
    • 父节点索引:(i - 1) / 2
    • 左子节点索引:2 * i + 1
    • 右子节点索引:2 * i + 2

2. 核心用法

(1)自然排序(元素实现Comparable)
import java.util.PriorityQueue;
import java.util.Queue;

// 任务类,按优先级(数字越小优先级越高)排序
class Task implements Comparable<Task> {
    private int priority; // 优先级
    private String name;

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

    // 最小堆:优先级小的排前面
    @Override
    public int compareTo(Task o) {
        return this.priority - o.priority;
    }

    @Override
    public String toString() {
        return "Task{priority=" + priority + ", name='" + name + "'}";
    }
}

public class PriorityQueueNaturalTest {
    public static void main(String[] args) {
        Queue<Task> queue = new PriorityQueue<>();

        // 添加任务(优先级:1>3>5)
        queue.offer(new Task(5, "普通任务"));
        queue.offer(new Task(1, "紧急任务"));
        queue.offer(new Task(3, "重要任务"));

        // 按优先级取出(紧急→重要→普通)
        while (!queue.isEmpty()) {
            System.out.println("处理任务:" + queue.poll());
        }
        // 输出:
        // 处理任务:Task{priority=1, name='紧急任务'}
        // 处理任务:Task{priority=3, name='重要任务'}
        // 处理任务:Task{priority=5, name='普通任务'}
    }
}
(2)自定义排序(最大堆)
import java.util.Comparator;
import java.util.PriorityQueue;

public class PriorityQueueCustomTest {
    public static void main(String[] args) {
        // 自定义比较器:最大堆(数字越大优先级越高)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());

        maxHeap.offer(3);
        maxHeap.offer(1);
        maxHeap.offer(5);

        // 按优先级取出(5→3→1)
        while (!maxHeap.isEmpty()) {
            System.out.println("取出元素:" + maxHeap.poll());
        }
    }
}

3. 核心特性总结

特性 PriorityQueue
底层结构 二叉堆(数组实现)
排序规则 默认最小堆,支持自定义Comparator
线程安全 非线程安全
允许null 不允许
时间复杂度 插入/删除:O(logn),查看堆顶:O(1)
适用场景 任务调度、事件优先级处理、TopN问题

4. 经典应用:TopN问题

用PriorityQueue实现“找出数组中最大的3个数”:

import java.util.Comparator;
import java.util.PriorityQueue;

public class TopNTest {
    public static void main(String[] args) {
        int[] nums = {5, 2, 9, 1, 7, 3, 8};
        int n = 3;

        // 用最小堆实现TopN(堆大小固定为n,效率更高)
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(n);
        for (int num : nums) {
            if (minHeap.size() < n) {
                minHeap.offer(num);
            } else if (num > minHeap.peek()) {
                minHeap.poll();
                minHeap.offer(num);
            }
        }

        System.out.println("最大的" + n + "个数:" + minHeap); // [7, 8, 9]
    }
}

🎯 队列在线程池中的应用

线程池是队列最核心的实战场景,线程池的“任务队列”直接决定了线程池的行为,不同队列适配不同的线程池策略:

1. 线程池与队列的关系

提交任务

核心线程数已满?

核心线程处理任务

任务队列已满?

任务加入队列等待

最大线程数已满?

创建非核心线程处理

执行拒绝策略

2. 线程池常用队列及适配策略

队列类型 线程池策略 适用场景
ArrayBlockingQueue(有界) 固定核心线程数+有限最大线程数 资源可控的场景(如核心业务线程池)
LinkedBlockingQueue(无界) 固定核心线程数(最大=核心) 任务量不可预估,需避免拒绝任务
SynchronousQueue(同步队列) 缓存线程池(核心=0,最大=Integer.MAX_VALUE) 任务快速处理,无队列等待
PriorityBlockingQueue(优先级) 自定义线程池 需按优先级处理任务的场景

3. 实战示例:自定义线程池(ArrayBlockingQueue)

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolQueueTest {
    public static void main(String[] args) {
        // 自定义线程池:核心线程2,最大线程4,队列容量3
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // 核心线程数
                4, // 最大线程数
                60, TimeUnit.SECONDS, // 非核心线程空闲超时
                new ArrayBlockingQueue<>(3), // 有界任务队列
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者执行
        );

        // 提交8个任务,观察队列和线程的处理逻辑
        for (int i = 1; i <= 8; i++) {
            int taskId = i;
            executor.submit(() -> {
                try {
                    System.out.println("任务" + taskId + "由线程" + Thread.currentThread().getName() + "处理");
                    Thread.sleep(1000); // 模拟任务耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        executor.shutdown();
    }
}

执行逻辑分析

  1. 任务1、2:核心线程处理;
  2. 任务3、4、5:加入队列等待;
  3. 任务6、7:创建非核心线程处理(最大线程4);
  4. 任务8:队列满+最大线程满,执行拒绝策略(调用者线程处理)。

📌 核心总结

队列的核心是“按规则存取元素”,不同队列的选择关键在于“顺序、并发、优先级”,核心要点如下:

  1. 基础队列:LinkedList实现Queue,是普通FIFO队列的首选,Deque(ArrayDeque)替代Stack,支持双端操作;
  2. 阻塞队列:ArrayBlockingQueue(有界、单锁)适用于固定容量场景,LinkedBlockingQueue(无界、双锁)适用于高并发场景,是生产者-消费者模型的核心;
  3. 优先级队列:PriorityQueue基于二叉堆实现,支持按优先级存取,适用于任务调度、TopN问题;
  4. 线程池应用:有界队列(ArrayBlockingQueue)控制资源,无界队列(LinkedBlockingQueue)避免拒绝任务,同步队列(SynchronousQueue)快速处理任务;
  5. 选型原则:普通场景用LinkedList/ArrayDeque,并发场景用ArrayBlockingQueue/LinkedBlockingQueue,优先级场景用PriorityQueue。

掌握这些,你就能根据业务场景选择最合适的队列实现类,无论是普通的任务排队,还是高并发的线程池设计,都能精准匹配需求——真正理解队列“从排队到优先处理”的设计精髓。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐