1. 引言

本文将深入探讨 Java 中的 Queue 接口。

首先,我们来 看看 Queue 是做什么的,以及它的一些核心方法。接着,我们将介绍 Java 标准库中提供的 几种常见的 Queue 实现

最后,我们还会聊一聊线程安全相关的内容,然后做个总结。

2. 队列的可视化理解

先来一个简单的比喻。

假设你刚开了一个小摊——热狗摊。为了高效服务顾客,我们决定一次只服务一个人。我们让顾客排成一队,新来的顾客从队尾加入。靠着良好的组织能力,我们就能公平地分发热狗了。

Java 中的 队列(Queue) 工作方式与此类似。当我们声明一个 Queue 后,可以将新元素添加到队尾,然后从队头取出。

实际上,大多数我们在 Java 中遇到的队列都是先进先出(FIFO) 的——即先进入的元素先被处理。

不过,有一个例外我们稍后会提到 —— 优先队列

3. 核心方法

Queue 接口定义了一些 必须由实现类实现的方法下面列出几个比较关键的方法:

  1. offer() – 将一个新元素插入队列
  2. poll() – 移除并返回队列头部的元素
  3. peek() – 查看队列头部的元素,但不移除它

4. AbstractQueue

AbstractQueue 是 Java 提供的 最简单的 Queue 实现类。它提供了一些 Queue 接口中方法的骨架实现,但不包括 offer 方法。

当我们自定义一个继承自 AbstractQueue 的队列时必须实现 offer 方法,并且不允许插入 null 元素。

此外,我们还需要提供以下方法的实现:peekpollsizejava.util 包中的 iterator

下面我们来用 AbstractQueue 实现一个简单的队列。

首先,定义一个类,内部使用 LinkedList 来存储队列元素:

public class CustomBaeldungQueue<T> extends AbstractQueue<T> {

    private LinkedList<T> elements;

    public CustomBaeldungQueue() {
      this.elements = new LinkedList<T>();
    }

}

接着,重写必须的方法并提供实现:

@Override
public Iterator<T> iterator() {
    return elements.iterator();
}

@Override
public int size() {
    return elements.size();
}

@Override
public boolean offer(T t) {
    if(t == null) return false;
    elements.add(t);
    return true;
}

@Override
public T poll() {
    Iterator<T> iter = elements.iterator();
    T t = iter.next();
    if(t != null){
        iter.remove();
        return t;
    }
    return null;
}

@Override
public T peek() {
    return elements.getFirst();
}

✅ 简单粗暴地测试一下:

customQueue.add(7);
customQueue.add(5);

int first = customQueue.poll();
int second = customQueue.poll();

assertEquals(7, first);
assertEquals(5, second);

5. 子接口

一般来说,Queue 接口有 三个主要的子接口BlockingQueueTransferQueueDeque

这三个接口涵盖了 Java 中绝大多数的队列实现。下面我们快速了解一下它们的作用。

5.1. BlockingQueue

BlockingQueue 接口 支持额外的操作,可以在队列状态不满足条件时阻塞线程。例如,当尝试从队列中取元素时,如果队列为空,线程会等待;当尝试添加元素时,如果队列已满,线程也会等待。

常见的 BlockingQueue 实现有:LinkedBlockingQueue、*SynchronousQueue* 和 ArrayBlockingQueue

更多信息可以参考我们之前的文章:BlockingQueue 详解

5.2. TransferQueue

TransferQueue 接口继承自 BlockingQueue,但更专注于 生产者-消费者模式。它能够控制数据从生产者到消费者的流动,从而实现反压机制。

Java 提供了一个实现类:*LinkedTransferQueue*。

5.3. Deque

Deque双端队列(Double-Ended Queue)的缩写,就像一副牌一样——元素可以从两端插入或取出。和普通队列类似,Deque 提供了从队首和队尾添加、获取和查看元素的方法

如果想深入了解 Deque 的使用,可以参考我们的文章:ArrayDeque 使用指南

6. 优先队列(Priority Queues)

前面提到,大多数 Java 队列遵循 FIFO 原则。

PriorityQueue 是个例外。插入元素时,它们会根据自然顺序排序,或者根据构造时传入的 Comparator 进行排序

来看一个简单的单元测试示例:

PriorityQueue<Integer> integerQueue = new PriorityQueue<>();

integerQueue.add(9);
integerQueue.add(2);
integerQueue.add(4);

int first = integerQueue.poll();
int second = integerQueue.poll();
int third = integerQueue.poll();

assertEquals(2, first);
assertEquals(4, second);
assertEquals(9, third);

⚠️ 虽然我们是按 9、2、4 的顺序添加的,但取出时是按自然排序的 2、4、9。

对字符串也是一样的效果:

PriorityQueue<String> stringQueue = new PriorityQueue<>();

stringQueue.add("blueberry");
stringQueue.add("apple");
stringQueue.add("cherry");

String first = stringQueue.poll();
String second = stringQueue.poll();
String third = stringQueue.poll();

assertEquals("apple", first);
assertEquals("blueberry", second);
assertEquals("cherry", third);

7. 线程安全

在多线程环境中,使用队列是非常常见的场景。队列可以在多个线程之间共享,并在空间不足时阻塞线程,帮助我们解决一些典型的并发问题。

比如,多个线程同时写入一个磁盘文件会导致资源竞争,进而影响性能。*通过使用单个写入线程配合 BlockingQueue,可以有效缓解这个问题,大幅提升写入效率。*

幸运的是,Java 提供了几个线程安全的队列实现,比如 ConcurrentLinkedQueueArrayBlockingQueueConcurrentLinkedDeque,非常适合在多线程程序中使用。

8. 总结

在这篇文章中,我们深入探讨了 Java 的 Queue 接口。

✅ 首先了解了 Queue 的基本功能,以及 Java 提供的常见实现。

✅ 然后讲解了标准的 FIFO 原则,以及 PriorityQueue 如何打破这一规则。

✅ 最后,我们还讨论了线程安全问题,以及如何在并发环境中使用队列。

一如既往,所有示例代码都可以在 GitHub 上找到。


原始标题:Guide to the Java Queue Interface