Java 实例 – 队列(Queue)用法(长文解析)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言

在编程世界中,队列(Queue)是一种常见的数据结构,它遵循“先进先出”(FIFO, First-In-First-Out)的原则。想象你站在电影院的售票窗口前,前面的人排成一列,每个人按顺序购票——这就是队列的经典应用场景。在 Java 中,队列通过 java.util.Queue 接口及其多种实现类(如 LinkedListArrayDequePriorityQueue 等)提供丰富的功能。本文将从基础概念、核心方法、实现类差异、线程安全队列到实际案例,逐步解析 Java 队列的用法,并结合代码示例帮助读者深入理解。


一、队列(Queue)的核心概念与接口

1.1 什么是队列?

队列是一种线性数据结构,其核心特点是“先进先出”。例如,打印机的任务队列、银行的客户等待队列,都符合这一特性。在 Java 中,队列通过 Queue 接口定义,该接口继承自 Collection 接口,因此具备集合的基本操作能力。

1.2 关键方法解析

以下方法是 Queue 接口的核心操作:

  • add(E e):将元素插入队列尾部,若队列已满会抛出异常(具体取决于实现类)。
  • offer(E e):与 add 类似,但队列满时返回 false 而非抛出异常。
  • remove():移除并返回队列头部的元素,若队列为空则抛出 NoSuchElementException
  • poll():移除并返回队列头部的元素,若队列为空则返回 null
  • element():返回队列头部元素但不移除,若队列为空则抛出异常。
  • peek():与 element 类似,但队列为空时返回 null

比喻说明

  • add/remove/element 类似“强硬”的操作,直接执行动作但可能引发错误;
  • offer/poll/peek 则像“礼貌询问”,在队列为空时返回友好结果而非强制报错。

二、常用队列实现类详解

Java 提供了多个 Queue 的实现类,每个类在性能、线程安全性和使用场景上有所差异。以下是几个核心实现的对比与示例:

2.1 LinkedList:灵活的双端队列

LinkedListQueue 接口的常见实现,它同时实现了 Deque(双端队列)接口。虽然 LinkedList 的插入和删除操作在链表中间效率较低,但在队列头部和尾部操作(如 addFirstremoveLast)时表现良好。

示例代码:

Queue<String> linkedQueue = new LinkedList<>();  
linkedQueue.offer("任务1");  
linkedQueue.offer("任务2");  
System.out.println(linkedQueue.poll());  // 输出 "任务1"  
System.out.println(linkedQueue.peek());  // 输出 "任务2"  

2.2 ArrayDeque:高效的数组实现

ArrayDeque 是基于数组的队列实现,其名称来源于“Array”和“Deque”的组合。它在头部和尾部的插入、删除操作均为 O(1) 时间复杂度,性能优于 LinkedList。但 ArrayDeque 不支持 null 元素,需注意使用场景。

示例代码:

Queue<Integer> arrayQueue = new ArrayDeque<>();  
arrayQueue.add(100);  
arrayQueue.add(200);  
System.out.println(arrayQueue.remove());  // 输出 100  
System.out.println(arrayQueue.size());    // 输出 1  

2.3 PriorityQueue:按优先级排序的队列

PriorityQueue 的特殊之处在于元素按优先级(自然顺序或自定义比较器)排序,头部始终是最小(或最大)的元素。例如,医院急诊室的“危重患者优先”规则,可以用 PriorityQueue 实现。

示例代码(自然排序):

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();  
priorityQueue.offer(50);  
priorityQueue.offer(30);  
priorityQueue.offer(70);  
System.out.println(priorityQueue.poll());  // 输出 30(最小值)  

示例代码(自定义比较器):

// 按字符串长度降序排列  
Comparator<String> comparator = (s1, s2) -> Integer.compare(s2.length(), s1.length());  
PriorityQueue<String> customQueue = new PriorityQueue<>(comparator);  
customQueue.add("apple");  
customQueue.add("banana");  
customQueue.add("kiwi");  
System.out.println(customQueue.poll());  // 输出 "banana"(长度最长)  

三、线程安全队列与并发场景

在多线程环境下,普通队列(如 LinkedListArrayDeque)可能因并发操作导致数据不一致或异常。Java 提供了线程安全的队列实现,如 ConcurrentLinkedQueueBlockingQueue

3.1 ConcurrentLinkedQueue:无阻塞线程安全队列

ConcurrentLinkedQueue 是基于链表的无界线程安全队列,通过原子操作(CAS)实现高效并发访问。它不提供阻塞方法,适合“生产者-消费者”模式中无需等待的场景。

示例代码:

ConcurrentLinkedQueue<String> concurrentQueue = new ConcurrentLinkedQueue<>();  
concurrentQueue.add("线程安全任务");  
System.out.println(concurrentQueue.poll());  // 输出 "线程安全任务"  

3.2 BlockingQueue:带阻塞功能的队列

BlockingQueue 接口(如 LinkedBlockingQueueArrayBlockingQueue)在队列为空或满时会阻塞线程,直到满足条件。这是实现生产者-消费者模式的首选工具。

示例代码(生产者-消费者模式):

// 定义一个容量为3的阻塞队列  
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);  

// 生产者线程  
new Thread(() -> {  
    try {  
        blockingQueue.put("任务A");  
        blockingQueue.put("任务B");  
        blockingQueue.put("任务C");  
        blockingQueue.put("任务D");  // 此时队列已满,线程阻塞  
    } catch (InterruptedException e) {  
        Thread.currentThread().interrupt();  
    }  
}).start();  

// 消费者线程  
new Thread(() -> {  
    try {  
        TimeUnit.SECONDS.sleep(1);  // 模拟延迟  
        System.out.println(blockingQueue.take());  // 输出 "任务A"  
        System.out.println(blockingQueue.take());  // 输出 "任务B"  
    } catch (InterruptedException e) {  
        Thread.currentThread().interrupt();  
    }  
}).start();  

四、实战案例:任务调度系统

4.1 场景描述

假设我们需要开发一个简单的任务调度系统,要求:

  1. 支持动态添加任务;
  2. 任务按顺序执行;
  3. 允许多个消费者(线程)同时处理任务。

4.2 实现思路

使用 ArrayBlockingQueue 作为任务队列,结合线程池实现消费者线程的并行处理。

完整代码示例:

import java.util.concurrent.*;  

// 定义任务类  
class Task {  
    private String name;  
    public Task(String name) { this.name = name; }  
    public void execute() {  
        System.out.println(Thread.currentThread().getName() + " 正在执行任务:" + name);  
    }  
}  

public class TaskScheduler {  
    private static final int QUEUE_CAPACITY = 5;  
    private static BlockingQueue<Task> taskQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);  
    private static ExecutorService executor = Executors.newFixedThreadPool(2);  

    // 生产者方法:添加任务到队列  
    public static void addTask(Task task) {  
        try {  
            taskQueue.put(task);  
            System.out.println("任务 " + task.name + " 已加入队列");  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
        }  
    }  

    // 消费者线程:从队列获取任务并执行  
    private static class TaskConsumer implements Runnable {  
        @Override  
        public void run() {  
            while (!Thread.currentThread().isInterrupted()) {  
                try {  
                    Task task = taskQueue.take();  
                    task.execute();  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                }  
            }  
        }  
    }  

    public static void main(String[] args) {  
        // 启动消费者线程  
        executor.submit(new TaskConsumer());  
        executor.submit(new TaskConsumer());  

        // 模拟生产任务  
        for (int i = 1; i <= 7; i++) {  
            addTask(new Task("任务" + i));  
        }  

        // 关闭线程池  
        executor.shutdown();  
        try {  
            executor.awaitTermination(1, TimeUnit.MINUTES);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  

运行结果示例:

任务 任务1 已加入队列  
任务 任务2 已加入队列  
任务 任务3 已加入队列  
任务 任务4 已加入队列  
任务 任务5 已加入队列  
任务 任务6 已加入队列  
任务 任务7 已加入队列  
pool-1-thread-1 正在执行任务:任务1  
pool-1-thread-2 正在执行任务:任务2  
pool-1-thread-1 正在执行任务:任务3  
pool-1-thread-2 正在执行任务:任务4  
pool-1-thread-1 正在执行任务:任务5  
pool-1-thread-2 正在执行任务:任务6  
pool-1-thread-1 正在执行任务:任务7  

结论

通过本文的讲解,读者应能掌握 Java 队列的基本概念、核心方法、实现类差异以及线程安全场景的解决方案。队列不仅是数据结构的基础工具,更是构建高并发系统的关键组件。建议读者通过实际项目(如任务调度、消息队列)进一步练习,以加深对队列的理解。

关键词自然布局:本文通过代码示例、场景分析和对比表格,深入探讨了“Java 实例 – 队列(Queue)用法”,帮助开发者选择最适合的实现类并解决实际问题。

最新发布