1. 队列基础概念与核心原理
队列(Queue)作为计算机科学中最基础的数据结构之一,其核心特性可以概括为"先进先出"(FIFO)原则。这种特性使得队列在需要按顺序处理数据的场景中表现出色。从操作系统中的进程调度,到网络通信中的数据包传输,再到现代分布式系统中的消息队列,队列的应用无处不在。
1.1 队列的抽象数据类型
队列的ADT(抽象数据类型)定义了以下基本操作:
- enqueue(element):在队尾添加元素
- dequeue():移除并返回队首元素
- peek()/front():查看队首元素但不移除
- isEmpty():检查队列是否为空
- size():返回队列中元素数量
这些操作的时间复杂度在不同实现方式下会有所差异。例如,使用链表实现的队列,其enqueue和dequeue操作都可以在O(1)时间内完成;而使用固定大小数组实现的队列,则可能面临扩容带来的O(n)时间复杂度问题。
1.2 队列的底层实现方式
在Java中,队列主要有以下几种实现方式:
- 数组实现:
public class ArrayQueue<E> { private E[] elements; private int front; private int rear; private int size; public ArrayQueue(int capacity) { elements = (E[]) new Object[capacity]; } public void enqueue(E element) { if (size == elements.length) { throw new IllegalStateException("Queue is full"); } elements[rear] = element; rear = (rear + 1) % elements.length; size++; } public E dequeue() { if (isEmpty()) { throw new NoSuchElementException(); } E element = elements[front]; elements[front] = null; front = (front + 1) % elements.length; size--; return element; } }- 链表实现:
public class LinkedQueue<E> { private static class Node<E> { E data; Node<E> next; Node(E data) { this.data = data; } } private Node<E> front; private Node<E> rear; private int size; public void enqueue(E element) { Node<E> newNode = new Node<>(element); if (rear != null) { rear.next = newNode; } rear = newNode; if (front == null) { front = rear; } size++; } public E dequeue() { if (isEmpty()) { throw new NoSuchElementException(); } E element = front.data; front = front.next; if (front == null) { rear = null; } size--; return element; } }- Java集合框架中的队列实现:
- LinkedList:实现了Deque接口,可用作队列
- ArrayDeque:基于循环数组的双端队列实现
- PriorityQueue:基于堆的优先级队列
- BlockingQueue接口的各种实现(如ArrayBlockingQueue、LinkedBlockingQueue)
注意:在实际开发中,除非有特殊需求,否则建议直接使用Java集合框架提供的队列实现,而不是自己从头实现。这些实现经过了充分优化和测试,性能更有保障。
1.3 队列的变体与应用场景
除了基本队列外,还存在多种队列变体,各自适用于不同的场景:
双端队列(Deque):两端都可以进行入队和出队操作,适合需要从两端操作数据的场景,如滑动窗口算法。
优先级队列(PriorityQueue):元素按优先级出队,而非先进先出。常用于任务调度、Dijkstra算法等场景。
循环队列:通过模运算实现队尾和队首的循环连接,避免数据搬移,提高性能。
阻塞队列(BlockingQueue):当队列为空时,获取操作会被阻塞;当队列满时,插入操作会被阻塞。这是Java并发编程中的重要工具。
延迟队列(DelayQueue):元素只有在延迟期满后才能被取出,适用于定时任务调度。
2. Java中的队列实现详解
Java集合框架提供了丰富的队列实现,每种实现都有其特定的使用场景和性能特征。深入理解这些实现的特点,能够帮助我们在实际开发中做出更合理的选择。
2.1 Queue接口核心方法
Java中的Queue接口定义了以下关键方法:
public interface Queue<E> extends Collection<E> { // 插入元素,成功时返回true,失败时抛出异常 boolean add(E e); // 插入元素,成功时返回true,失败时返回false boolean offer(E e); // 移除并返回队首元素,队列为空时抛出异常 E remove(); // 移除并返回队首元素,队列为空时返回null E poll(); // 查看队首元素但不移除,队列为空时抛出异常 E element(); // 查看队首元素但不移除,队列为空时返回null E peek(); }在实际使用中,通常推荐使用offer-poll-peek组合,而不是add-remove-element组合,因为前者在操作失败时不会抛出异常,行为更加友好。
2.2 主要队列实现类比较
| 实现类 | 底层结构 | 线程安全 | 边界 | 特点 |
|---|---|---|---|---|
| LinkedList | 双向链表 | 否 | 无界 | 支持快速插入删除,随机访问慢 |
| ArrayDeque | 循环数组 | 否 | 无界 | 内存效率高,性能好 |
| PriorityQueue | 堆 | 否 | 无界 | 元素按自然顺序或Comparator排序 |
| ArrayBlockingQueue | 数组 | 是 | 有界 | 固定大小,阻塞操作 |
| LinkedBlockingQueue | 链表 | 是 | 可选有界 | 默认无界,可指定容量 |
| PriorityBlockingQueue | 堆 | 是 | 无界 | 线程安全的优先级队列 |
| DelayQueue | 优先级队列 | 是 | 无界 | 元素需实现Delayed接口 |
| SynchronousQueue | 无存储 | 是 | 有界(0) | 直接传递,不存储元素 |
2.3 阻塞队列深入解析
阻塞队列是Java并发编程中的重要工具,它们实现了BlockingQueue接口,提供了线程安全的队列操作,并支持阻塞式的插入和移除操作。
ArrayBlockingQueue示例:
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); // 生产者线程 new Thread(() -> { try { for (int i = 0; i < 100; i++) { queue.put(i); // 队列满时会阻塞 System.out.println("Produced: " + i); Thread.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // 消费者线程 new Thread(() -> { try { while (true) { Integer item = queue.take(); // 队列空时会阻塞 System.out.println("Consumed: " + item); Thread.sleep(200); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();关键点:
- put()和take()是阻塞方法,会在队列满/空时阻塞线程
- offer(e, timeout, unit)和poll(timeout, unit)是带超时的非阻塞方法
- 适用于生产者-消费者模式,可以有效协调不同速度的生产者和消费者
实际经验:在使用阻塞队列时,一定要正确处理InterruptedException。通常的做法是恢复中断状态(调用Thread.currentThread().interrupt()),而不是简单地忽略异常。
2.4 优先级队列的实现原理
PriorityQueue是基于堆(通常是最小堆)实现的优先级队列。堆是一种完全二叉树,满足父节点的值总是小于或等于子节点的值(最小堆)。
堆的基本操作:
- 上浮(siftUp):当新元素添加到堆末尾时,与其父节点比较并交换,直到满足堆性质
- 下沉(siftDown):当移除堆顶元素时,将末尾元素移到堆顶,然后与其子节点比较并交换,直到满足堆性质
PriorityQueue核心源码分析:
public class PriorityQueue<E> extends AbstractQueue<E> { private transient Object[] queue; // 存储元素的数组 private int size; // 元素数量 private final Comparator<? super E> comparator; // 比较器 // 插入元素 public boolean offer(E e) { if (e == null) throw new NullPointerException(); int i = size; if (i >= queue.length) grow(i + 1); // 扩容 size = i + 1; if (i == 0) { queue[0] = e; } else { siftUp(i, e); // 上浮操作 } return true; } // 移除并返回队首元素 public E poll() { if (size == 0) return null; int s = --size; E result = (E) queue[0]; E x = (E) queue[s]; queue[s] = null; if (s != 0) siftDown(0, x); // 下沉操作 return result; } // 上浮操作 private void siftUp(int k, E x) { if (comparator != null) { siftUpUsingComparator(k, x); } else { siftUpComparable(k, x); } } // 使用自然排序的上浮 private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { int parent = (k - 1) >>> 1; // 父节点索引 Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = key; } }使用场景:
- 任务调度系统(按优先级执行任务)
- 求Top K问题(维护一个大小为K的最小堆)
- 图算法(如Dijkstra算法)
- 合并多个有序序列
3. 队列的高频实战应用
队列在实际开发中有着广泛的应用场景。掌握这些常见应用模式,能够帮助我们更好地解决实际问题。
3.1 生产者-消费者模式
生产者-消费者模式是多线程编程中的经典模式,队列在其中扮演着缓冲区的角色,解耦生产者和消费者。
完整实现示例:
public class ProducerConsumerExample { private static final int QUEUE_CAPACITY = 5; private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); static class Producer implements Runnable { private final String name; Producer(String name) { this.name = name; } @Override public void run() { try { for (int i = 0; i < 10; i++) { queue.put(i); // 阻塞直到有空间 System.out.printf("[%s] 生产: %d, 队列大小: %d%n", name, i, queue.size()); Thread.sleep((long) (Math.random() * 1000)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } static class Consumer implements Runnable { private final String name; Consumer(String name) { this.name = name; } @Override public void run() { try { while (true) { Integer item = queue.take(); // 阻塞直到有元素 System.out.printf("[%s] 消费: %d, 队列大小: %d%n", name, item, queue.size()); Thread.sleep((long) (Math.random() * 2000)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { // 创建2个生产者和3个消费者 ExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(new Producer("生产者1")); executor.submit(new Producer("生产者2")); executor.submit(new Consumer("消费者1")); executor.submit(new Consumer("消费者2")); executor.submit(new Consumer("消费者3")); // 运行10秒后关闭 try { Thread.sleep(10000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } executor.shutdownNow(); } }关键点:
- 使用BlockingQueue作为共享缓冲区,自动处理线程同步问题
- put()和take()方法会自动阻塞,简化了线程间协调
- 生产者和消费者可以以不同的速度运行,队列起到缓冲作用
- 可以轻松扩展为多生产者-多消费者模型
实际经验:在生产环境中,通常会使用更高级的消息队列(如RabbitMQ、Kafka)来实现分布式系统中的生产者-消费者模式,但基本原理是相通的。
3.2 线程池任务队列
Java的ThreadPoolExecutor内部使用BlockingQueue来存储待执行的任务。理解这一点对于合理配置线程池非常重要。
线程池队列策略:
直接传递(SynchronousQueue):
- 无容量,每个插入操作必须等待一个移除操作
- 适用于任务处理速度快的场景
- 通常需要较大的maximumPoolSize
无界队列(LinkedBlockingQueue):
- 新任务到来时,如果核心线程都在忙,则进入队列等待
- 适用于任务提交速度可能瞬时高于处理速度,但长期来看处理速度能跟上的场景
- maximumPoolSize参数无效
有界队列(ArrayBlockingQueue):
- 队列满时会创建新线程(直到达到maximumPoolSize)
- 适用于需要防止资源耗尽的情况
- 需要合理设置队列大小和maximumPoolSize
自定义线程池示例:
public class ThreadPoolConfig { // CPU密集型任务 public static ExecutorService newCpuIntensiveThreadPool() { int corePoolSize = Runtime.getRuntime().availableProcessors(); int maxPoolSize = corePoolSize * 2; return new ThreadPoolExecutor( corePoolSize, maxPoolSize, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy()); } // IO密集型任务 public static ExecutorService newIoIntensiveThreadPool() { int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; int maxPoolSize = corePoolSize * 4; return new ThreadPoolExecutor( corePoolSize, maxPoolSize, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy()); } }拒绝策略选择:
- AbortPolicy:默认策略,直接抛出RejectedExecutionException
- CallerRunsPolicy:由调用线程直接执行任务
- DiscardPolicy:直接丢弃任务
- DiscardOldestPolicy:丢弃队列中最老的任务,然后重试
3.3 消息队列应用
虽然Java内置的队列实现适合单机应用,但在分布式系统中,我们通常需要使用专门的消息队列中间件,如RabbitMQ、Kafka等。这些系统本质上也是基于队列的概念构建的。
RabbitMQ基本使用示例:
public class RabbitMQExample { private final static String QUEUE_NAME = "hello"; // 生产者 public static void sendMessage() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.queueDeclare(QUEUE_NAME, false, false, false, null); String message = "Hello World!"; channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); } } // 消费者 public static void receiveMessage() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); System.out.println(" [*] Waiting for messages..."); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [x] Received '" + message + "'"); }; channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {}); } }消息队列的核心概念:
- 生产者(Publisher):发送消息的程序
- 消费者(Consumer):接收消息的程序
- 队列(Queue):存储消息的缓冲区
- 交换机(Exchange):接收生产者发送的消息并根据规则路由到队列
- 绑定(Binding):连接交换机和队列的规则
3.4 算法中的应用
队列在算法中有着广泛的应用,特别是在广度优先搜索(BFS)和缓存淘汰算法中。
BFS算法示例:
public class BFS { public static void bfs(Map<Integer, List<Integer>> graph, int start) { Queue<Integer> queue = new LinkedList<>(); Set<Integer> visited = new HashSet<>(); queue.offer(start); visited.add(start); while (!queue.isEmpty()) { int node = queue.poll(); System.out.println("访问节点: " + node); for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) { if (!visited.contains(neighbor)) { visited.add(neighbor); queue.offer(neighbor); } } } } public static void main(String[] args) { Map<Integer, List<Integer>> graph = new HashMap<>(); graph.put(0, Arrays.asList(1, 2)); graph.put(1, Arrays.asList(2)); graph.put(2, Arrays.asList(0, 3)); graph.put(3, Arrays.asList(3)); bfs(graph, 2); } }LRU缓存实现:
public class LRUCache<K, V> { private final int capacity; private final Map<K, V> map; private final Deque<K> queue; public LRUCache(int capacity) { this.capacity = capacity; this.map = new HashMap<>(); this.queue = new LinkedList<>(); } public V get(K key) { if (map.containsKey(key)) { // 将访问的元素移到队列前端 queue.remove(key); queue.addFirst(key); return map.get(key); } return null; } public void put(K key, V value) { if (map.containsKey(key)) { queue.remove(key); } else if (map.size() == capacity) { // 移除最久未使用的元素 K oldestKey = queue.removeLast(); map.remove(oldestKey); } queue.addFirst(key); map.put(key, value); } }4. 队列使用中的常见问题与优化
在实际开发中使用队列时,会遇到各种性能问题和并发问题。了解这些问题及其解决方案,可以帮助我们编写更健壮的代码。
4.1 性能问题与优化
1. 队列选择不当导致的性能问题
不同的队列实现在不同场景下性能差异很大。例如:
- LinkedList在频繁插入删除时表现良好,但随机访问性能差
- ArrayDeque在大多数情况下性能优于LinkedList,内存占用更小
- PriorityQueue的插入和删除操作时间复杂度为O(log n),不适合高频操作
优化建议:
- 对于单线程环境,优先考虑ArrayDeque
- 需要优先级排序时使用PriorityQueue
- 并发环境下根据需求选择合适的BlockingQueue实现
2. 队列大小设置不合理
有界队列大小设置不当会导致:
- 队列过小:频繁触发拒绝策略或导致生产者阻塞
- 队列过大:内存占用高,任务响应延迟增加
优化建议:
- 根据系统负载测试确定合适的队列大小
- 考虑使用动态调整队列大小的策略
- 监控队列长度,设置合理的告警阈值
3. 内存占用问题
无界队列可能导致内存耗尽:
// 危险:无界队列可能导致OOM BlockingQueue<byte[]> queue = new LinkedBlockingQueue<>(); while (true) { queue.put(new byte[10_000_000]); // 每次添加10MB }解决方案:
- 使用有界队列
- 实施背压机制(Backpressure)
- 监控队列大小并设置合理的拒绝策略
4.2 并发问题与解决方案
1. 生产者-消费者速度不匹配
当生产者速度远大于消费者速度时,会导致:
- 队列不断增长,内存占用增加
- 任务响应时间变长
解决方案:
- 使用合适的拒绝策略(如CallerRunsPolicy)
- 动态调整消费者数量
- 实现背压机制,降低生产者速度
2. 死锁问题
不正确的锁顺序可能导致死锁:
// 危险:可能导致死锁 public class DeadlockExample { private final Queue<Integer> queue1 = new LinkedList<>(); private final Queue<Integer> queue2 = new LinkedList<>(); public void transfer() { synchronized (queue1) { synchronized (queue2) { while (!queue1.isEmpty()) { queue2.add(queue1.poll()); } } } } public void reverseTransfer() { synchronized (queue2) { // 与transfer()的锁顺序相反 synchronized (queue1) { while (!queue2.isEmpty()) { queue1.add(queue2.poll()); } } } } }解决方案:
- 保持一致的锁获取顺序
- 使用单个锁保护多个队列
- 考虑使用并发队列实现
3. 伪共享问题
在多核CPU环境下,队列的头尾指针可能位于同一缓存行,导致伪共享(False Sharing),降低性能。
优化方案:
- 使用填充(Padding)隔离频繁修改的字段
public class PaddedAtomicReference<T> extends AtomicReference<T> { // 填充缓存行(通常64字节) public volatile long p1, p2, p3, p4, p5, p6, p7; public PaddedAtomicReference(T initialValue) { super(initialValue); } }- Java 8+可以使用@Contended注解(需要开启JVM参数-XX:-RestrictContended)
4.3 监控与调优
1. 队列监控指标
在生产环境中,应该监控以下队列相关指标:
- 当前队列大小
- 入队/出队速率
- 等待时间(从入队到出队的平均时间)
- 拒绝的任务数量
- 队列容量利用率
2. JMX监控示例
public class QueueMonitor implements QueueMonitorMBean { private final BlockingQueue<?> queue; public QueueMonitor(BlockingQueue<?> queue) { this.queue = queue; } @Override public int getQueueSize() { return queue.size(); } @Override public int getRemainingCapacity() { return queue.remainingCapacity(); } public static void register(BlockingQueue<?> queue, String name) throws Exception { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName mxbeanName = new ObjectName("com.example:type=QueueMonitor,name=" + name); QueueMonitor monitor = new QueueMonitor(queue); mbs.registerMBean(monitor, mxbeanName); } } public interface QueueMonitorMBean { int getQueueSize(); int getRemainingCapacity(); }3. 动态调整队列参数
根据监控指标动态调整队列参数:
public class DynamicThreadPool extends ThreadPoolExecutor { public DynamicThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } public void adjustPoolSize(int newCoreSize, int newMaxSize) { if (newCoreSize > getMaximumPoolSize()) { setMaximumPoolSize(newMaxSize); setCorePoolSize(newCoreSize); } else { setCorePoolSize(newCoreSize); setMaximumPoolSize(newMaxSize); } } public void adjustQueueCapacity(int newCapacity) { if (getQueue() instanceof ResizableBlockingQueue) { ((ResizableBlockingQueue<Runnable>) getQueue()).setCapacity(newCapacity); } } } interface ResizableBlockingQueue<E> extends BlockingQueue<E> { void setCapacity(int capacity); }4.4 常见问题排查
1. 队列操作阻塞时间过长
可能原因:
- 消费者处理速度太慢
- 队列容量设置不合理
- 系统资源不足(CPU、IO等)
排查方法:
- 检查消费者线程的堆栈跟踪
- 监控队列大小变化
- 检查系统资源使用情况
2. 内存泄漏
症状:
- 队列大小持续增长不下降
- 最终导致OutOfMemoryError
常见原因:
- 消费者异常退出未正确处理
- 任务对象持有大内存引用
- 队列本身设计问题(如未正确实现移除逻辑)
解决方案:
- 加强异常处理
- 使用弱引用或软引用
- 实现定期清理机制
3. 数据不一致
症状:
- 队列中的元素顺序异常
- 元素丢失或重复
常见原因:
- 不正确的并发控制
- 生产者或消费者逻辑错误
- 队列实现本身的bug
解决方案:
- 使用线程安全的队列实现
- 添加适当的同步控制
- 实现幂等性处理
5. 高级队列模式与扩展
除了基本的队列用法外,在实际开发中还有一些高级队列模式和扩展用法,可以解决特定的问题。
5.1 工作窃取队列(Work Stealing Queue)
工作窃取是一种提高并行程序性能的技术,Java中的ForkJoinPool就是基于这种思想实现的。
基本原理:
- 每个工作者线程维护自己的双端队列
- 从队列头部获取任务执行
- 当自己的队列为空时,可以从其他线程队列的尾部"窃取"任务
实现示例:
public class WorkStealingQueueExample { private static class Task implements Runnable { private final int number; Task(int number) { this.number = number; } @Override public void run() { System.out.println("Task " + number + " executed by " + Thread.currentThread().getName()); try { Thread.sleep(100); // 模拟工作负载 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { // 创建工作窃取线程池 ExecutorService executor = Executors.newWorkStealingPool(); // 提交任务 for (int i = 0; i < 20; i++) { executor.submit(new Task(i)); } // 等待任务完成 try { executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }适用场景:
- 任务执行时间不均衡
- 任务之间存在依赖关系
- 需要最大化CPU利用率
5.2 延迟队列(DelayQueue)
DelayQueue是一个无界阻塞队列,其中的元素只有在其延迟期满后才能被取出。
实现要点:
- 元素必须实现Delayed接口
- 根据getDelay()方法返回值确定延迟时间
- 元素会根据延迟时间排序
示例代码:
public class DelayQueueExample { static class DelayedTask implements Delayed { private final String name; private final long startTime; // 执行时间(纳秒) DelayedTask(String name, long delayInMillis) { this.name = name; this.startTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delayInMillis, TimeUnit.MILLISECONDS); } @Override public long getDelay(TimeUnit unit) { long remaining = startTime - System.nanoTime(); return unit.convert(remaining, TimeUnit.NANOSECONDS); } @Override public int compareTo(Delayed other) { return Long.compare(this.startTime, ((DelayedTask) other).startTime); } @Override public String toString() { return "DelayedTask[" + name + ", " + TimeUnit.MILLISECONDS.convert( startTime - System.nanoTime(), TimeUnit.NANOSECONDS) + "ms]"; } } public static void main(String[] args) throws InterruptedException { DelayQueue<DelayedTask> queue = new DelayQueue<>(); queue.put(new DelayedTask("Task1", 2000)); queue.put(new DelayedTask("Task2", 1000)); queue.put(new DelayedTask("Task3", 3000)); while (!queue.isEmpty()) { DelayedTask task = queue.take(); System.out.println("Executing: " + task + " at " + System.currentTimeMillis()); } } }应用场景:
- 定时任务调度
- 缓存过期清理
- 超时处理
5.3 多级优先级队列
在某些场景下,简单的优先级队列可能无法满足需求,需要更复杂的多级优先级队列。
实现示例:
public class MultiLevelPriorityQueue<T> { private final List<Queue<T>> queues; private final List<Integer> priorities; public MultiLevelPriorityQueue(List<Integer> priorities) { this.priorities = new ArrayList<>(priorities); this.queues = new ArrayList<>(); for (int i = 0; i < priorities.size(); i++) { queues.add(new LinkedList<>()); } } public void enqueue(T item, int priority) { int index = priorities.indexOf(priority); if (index == -1) { throw new IllegalArgumentException("Invalid priority"); } queues.get(index).add(item); } public T dequeue() { for (Queue<T> queue : queues) { if (!queue.isEmpty()) { return queue.poll(); } } return null; } public static void main(String[] args) { MultiLevelPriorityQueue<String> queue = new MultiLevelPriorityQueue<>(Arrays.asList(3, 2, 1)); queue.enqueue("Low1", 3); queue.enqueue("High1", 1); queue.enqueue("Medium1", 2); queue.enqueue("Low2", 3); queue.enqueue("High2", 1); System.out.println(queue.dequeue()); // High1 System.out.println(queue.dequeue()); // High2 System.out.println(queue.dequeue()); // Medium1 System.out.println(queue.dequeue()); // Low1 System.out.println(queue.dequeue()); // Low2 } }应用场景:
- 操作系统进程调度
- 网络服务质量(QoS)管理
- 多租户资源分配
5.4 持久化队列
对于需要持久化的场景,可以将队列内容保存到磁盘,防止系统崩溃时数据丢失。
简单实现思路:
public class PersistentQueue<T extends Serializable> { private final Queue<T> memoryQueue = new LinkedList<>(); private final String storagePath; public PersistentQueue(String storagePath) { this.storagePath = storagePath; loadFromDisk(); } @SuppressWarnings("unchecked") private void loadFromDisk() { try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream(storagePath))) { memoryQueue.addAll((Collection<T>) ois.readObject()); } catch (FileNotFoundException e) { // 首次使用,无持久化文件 } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("Failed to load queue", e); } } private void saveToDisk() { try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(storagePath))) { oos.writeObject(new ArrayList<>(memoryQueue)); } catch (IOException e) { throw new RuntimeException("Failed to save queue", e); } } public synchronized void enqueue(T item) { memoryQueue.add(item); saveToDisk(); } public synchronized T dequeue() { T item = memoryQueue.poll(); if (item != null) { saveToDisk(); } return item; } }生产级解决方案:
- 使用现有的持久化队列库(如Chronicle Queue)
- 使用数据库实现队列(如MySQL、PostgreSQL)
- 使用专门的消息队列中间件(如RabbitMQ、Kafka)
5.5 分布式队列
在分布式系统中,可以使用Redis、ZooKeeper等中间件实现分布式队列。
Redis实现示例:
public class RedisQueue { private final JedisPool jedisPool; private final String queueKey; public RedisQueue(JedisPool jedisPool, String queueKey) { this.jedisPool = jedisPool; this.queueKey = queueKey; } public void enqueue(String item) { try (Jedis jedis = jedisPool.getResource()) { jedis.rpush(queueKey, item); } } public String dequeue() { try (Jedis jedis = jedisPool.getResource()) { return jedis.lpop(queueKey); } } public String blockingDequeue(int timeoutInSeconds) { try (Jedis jedis = jedisPool.getResource()) { List<String> result = jedis.blpop(timeoutInSeconds, queueKey); return result != null && result.size() > 1 ? result.get(1) : null; } } }注意事项:
- 考虑消息确认机制
- 实现消息重试和死信队列
- 处理网络分区和故障转移
- 监控队列积压情况
6. 队列性能测试与对比
了解不同队列实现的性能特征对于选择合适的队列实现至关重要。本节将通过实际测试对比各种队列实现的性能。
6.1 测试环境与方法
测试环境:
- CPU: Intel Core i7-10750H 2.60GHz
- 内存: 16GB DDR4
- OS: Windows 10
- JDK: OpenJDK 17.0.1
测试方法:
- 单线程性能测试:测量各种操作的平均耗时
- 并发性能测试:测量多线程环境下的吞吐量
- 内存占用测试:测量不同队列实现的内存开销
6.2 单线程性能测试
测试代码示例:
public class QueueBenchmark { private static final int OPERATION_COUNT = 1_000_000; public static void testQueue(Queue<Integer> queue, String name) { // 入队测试 long start = System.nanoTime(); for