1. 队列数据结构基础概念
队列(Queue)是一种先进先出(FIFO)的线性数据结构,就像现实生活中的排队场景一样,先来的人先获得服务。在计算机科学中,队列被广泛应用于各种场景,如任务调度、消息传递、广度优先搜索等。
队列的两个基本操作是:
- 入队(enqueue):在队列尾部添加元素
- 出队(dequeue):从队列头部移除元素
Java中队列通常还提供以下辅助操作:
- peek:查看队首元素但不移除
- isEmpty:检查队列是否为空
- size:获取队列中元素数量
2. Java中的队列实现方式
2.1 基于数组的实现
数组实现队列需要考虑循环使用数组空间的问题,避免"假溢出":
java复制public class ArrayQueue<E> {
private E[] elements;
private int front;
private int rear;
private int size;
private static final int DEFAULT_CAPACITY = 10;
public ArrayQueue() {
this(DEFAULT_CAPACITY);
}
public ArrayQueue(int capacity) {
elements = (E[]) new Object[capacity];
front = 0;
rear = -1;
size = 0;
}
public void enqueue(E element) {
if (isFull()) {
resize();
}
rear = (rear + 1) % elements.length;
elements[rear] = element;
size++;
}
public E dequeue() {
if (isEmpty()) {
throw new NoSuchElementException("Queue is empty");
}
E element = elements[front];
elements[front] = null;
front = (front + 1) % elements.length;
size--;
return element;
}
private boolean isFull() {
return size == elements.length;
}
private void resize() {
int newCapacity = elements.length * 2;
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[(front + i) % elements.length];
}
elements = newElements;
front = 0;
rear = size - 1;
}
}
注意事项:数组实现需要考虑动态扩容问题,否则队列大小固定。扩容时要注意元素的重新排列。
2.2 基于链表的实现
链表实现不需要考虑容量问题,但每个元素需要额外的指针空间:
java复制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) {
front = rear = newNode;
} else {
rear.next = newNode;
rear = newNode;
}
size++;
}
public E dequeue() {
if (isEmpty()) {
throw new NoSuchElementException("Queue is empty");
}
E element = front.data;
front = front.next;
if (front == null) {
rear = null;
}
size--;
return element;
}
}
实操心得:链表实现更简单,不需要处理扩容问题,但在高并发环境下需要考虑线程安全问题。
3. Java集合框架中的队列实现
Java集合框架提供了多种队列实现,各有特点:
3.1 LinkedList
java复制Queue<String> queue = new LinkedList<>();
queue.offer("A"); // 入队
queue.poll(); // 出队
特点:
- 基于双向链表实现
- 无界队列
- 支持null元素
- 非线程安全
3.2 ArrayDeque
java复制Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1);
queue.poll();
特点:
- 基于可扩容数组实现
- 性能优于LinkedList
- 不支持null元素
- 非线程安全
3.3 PriorityQueue
java复制Queue<Integer> pq = new PriorityQueue<>();
pq.offer(3);
pq.offer(1);
pq.offer(2);
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // 输出1,2,3
}
特点:
- 基于堆实现
- 元素按优先级出队
- 非线程安全
3.4 阻塞队列实现
Java并发包提供了多种线程安全的阻塞队列:
- ArrayBlockingQueue:有界数组实现
- LinkedBlockingQueue:可选有界链表实现
- PriorityBlockingQueue:无界优先级队列
- SynchronousQueue:不存储元素的特殊队列
java复制BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// 生产者线程
queue.put("message"); // 队列满时阻塞
// 消费者线程
String msg = queue.take(); // 队列空时阻塞
4. 队列的应用场景
4.1 任务调度系统
java复制public class TaskScheduler {
private final BlockingQueue<Runnable> taskQueue;
public TaskScheduler(int workerCount) {
taskQueue = new LinkedBlockingQueue<>();
for (int i = 0; i < workerCount; i++) {
new Thread(() -> {
while (true) {
try {
Runnable task = taskQueue.take();
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
}
}
public void schedule(Runnable task) {
taskQueue.offer(task);
}
}
4.2 广度优先搜索(BFS)
java复制public void bfs(Node start) {
Queue<Node> queue = new LinkedList<>();
Set<Node> visited = new HashSet<>();
queue.offer(start);
visited.add(start);
while (!queue.isEmpty()) {
Node current = queue.poll();
System.out.println(current);
for (Node neighbor : current.getNeighbors()) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.offer(neighbor);
}
}
}
}
4.3 消息队列中间件
虽然Java提供了本地队列实现,但在分布式系统中通常使用专业的消息队列中间件如RabbitMQ、Kafka等。这些系统本质上也是基于队列模型,但提供了持久化、分布式等高级特性。
5. 性能比较与选型建议
5.1 不同实现的性能对比
| 实现方式 | 入队时间复杂度 | 出队时间复杂度 | 空间复杂度 | 线程安全 |
|---|---|---|---|---|
| 数组队列 | O(1)均摊 | O(1) | O(n) | 否 |
| 链表队列 | O(1) | O(1) | O(n) | 否 |
| ArrayDeque | O(1)均摊 | O(1) | O(n) | 否 |
| LinkedList | O(1) | O(1) | O(n) | 否 |
| ArrayBlockingQueue | O(1) | O(1) | O(n) | 是 |
5.2 选型建议
-
单线程环境:
- 需要优先级:PriorityQueue
- 不需要优先级:ArrayDeque(性能最好)
-
多线程环境:
- 有界队列:ArrayBlockingQueue
- 无界队列:LinkedBlockingQueue
- 优先级队列:PriorityBlockingQueue
-
特殊需求:
- 延迟队列:DelayQueue
- 同步传输:SynchronousQueue
6. 常见问题与解决方案
6.1 队列已满异常
java复制try {
queue.add(element); // 可能抛出IllegalStateException
} catch (IllegalStateException e) {
// 处理队列已满情况
}
// 或者使用offer方法
if (!queue.offer(element)) {
// 处理添加失败情况
}
6.2 队列空异常
java复制try {
E element = queue.remove(); // 可能抛出NoSuchElementException
} catch (NoSuchElementException e) {
// 处理队列为空情况
}
// 或者使用poll方法
E element = queue.poll();
if (element == null) {
// 处理队列为空情况
}
6.3 多线程环境下的竞态条件
java复制// 错误示例
if (!queue.isEmpty()) {
// 这里可能有其他线程修改了队列状态
E element = queue.poll();
}
// 正确做法
E element = queue.poll();
if (element != null) {
// 处理元素
}
6.4 内存泄漏问题
在使用数组实现队列时,出队操作后要及时置空引用:
java复制public E dequeue() {
// ...
elements[front] = null; // 防止内存泄漏
// ...
}
7. 高级队列实现技巧
7.1 双端队列(Deque)
Java中的ArrayDeque和LinkedList都实现了Deque接口,支持从两端操作:
java复制Deque<String> deque = new ArrayDeque<>();
deque.offerFirst("A"); // 头部添加
deque.offerLast("B"); // 尾部添加
String first = deque.pollFirst(); // 头部移除
String last = deque.pollLast(); // 尾部移除
7.2 延迟队列(DelayQueue)
java复制class DelayedTask implements Delayed {
private final long executeTime;
private final String taskName;
public DelayedTask(long delay, String taskName) {
this.executeTime = System.currentTimeMillis() + delay;
this.taskName = taskName;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.executeTime, ((DelayedTask)o).executeTime);
}
}
// 使用示例
DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
delayQueue.put(new DelayedTask(5000, "Task1"));
DelayedTask task = delayQueue.take(); // 至少延迟5秒后才会返回
7.3 无锁队列实现
对于高性能场景,可以考虑无锁队列实现:
java复制import java.util.concurrent.atomic.AtomicReference;
public class LockFreeQueue<E> {
private static class Node<E> {
final E item;
AtomicReference<Node<E>> next;
Node(E item) {
this.item = item;
this.next = new AtomicReference<>(null);
}
}
private final AtomicReference<Node<E>> head;
private final AtomicReference<Node<E>> tail;
public LockFreeQueue() {
Node<E> dummy = new Node<>(null);
head = new AtomicReference<>(dummy);
tail = new AtomicReference<>(dummy);
}
public void enqueue(E item) {
Node<E> newNode = new Node<>(item);
while (true) {
Node<E> currentTail = tail.get();
Node<E> tailNext = currentTail.next.get();
if (currentTail == tail.get()) {
if (tailNext != null) {
// 有其他线程已经添加了节点但还没更新tail
tail.compareAndSet(currentTail, tailNext);
} else {
if (currentTail.next.compareAndSet(null, newNode)) {
tail.compareAndSet(currentTail, newNode);
return;
}
}
}
}
}
public E dequeue() {
while (true) {
Node<E> currentHead = head.get();
Node<E> currentTail = tail.get();
Node<E> headNext = currentHead.next.get();
if (currentHead == head.get()) {
if (currentHead == currentTail) {
if (headNext == null) {
return null;
}
tail.compareAndSet(currentTail, headNext);
} else {
E item = headNext.item;
if (head.compareAndSet(currentHead, headNext)) {
return item;
}
}
}
}
}
}
注意事项:无锁实现虽然性能高,但实现复杂,容易出错,一般场景建议使用Java内置的并发队列。
8. 测试队列实现的正确性
编写全面的单元测试验证队列实现:
java复制import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class QueueTest {
@Test
void testArrayQueue() {
testQueue(new ArrayQueue<>());
}
@Test
void testLinkedQueue() {
testQueue(new LinkedQueue<>());
}
void testQueue(Queue<Integer> queue) {
assertTrue(queue.isEmpty());
assertEquals(0, queue.size());
// 测试入队和出队
queue.enqueue(1);
assertFalse(queue.isEmpty());
assertEquals(1, queue.size());
queue.enqueue(2);
assertEquals(2, queue.size());
assertEquals(1, queue.dequeue());
assertEquals(1, queue.size());
assertEquals(2, queue.dequeue());
assertTrue(queue.isEmpty());
// 测试边界条件
assertThrows(NoSuchElementException.class, queue::dequeue);
// 测试大量元素
for (int i = 0; i < 1000; i++) {
queue.enqueue(i);
}
for (int i = 0; i < 1000; i++) {
assertEquals(i, queue.dequeue());
}
}
}
9. 队列的性能优化技巧
9.1 批量操作优化
对于频繁的入队出队操作,可以考虑批量处理:
java复制public class BatchQueue<E> {
private final Queue<E> queue;
private final int batchSize;
private final List<E> batchBuffer;
public BatchQueue(Queue<E> queue, int batchSize) {
this.queue = queue;
this.batchSize = batchSize;
this.batchBuffer = new ArrayList<>(batchSize);
}
public void enqueue(E element) {
batchBuffer.add(element);
if (batchBuffer.size() >= batchSize) {
flush();
}
}
public void flush() {
if (!batchBuffer.isEmpty()) {
queue.addAll(batchBuffer);
batchBuffer.clear();
}
}
public List<E> dequeueBatch(int maxSize) {
List<E> result = new ArrayList<>(maxSize);
while (result.size() < maxSize && !queue.isEmpty()) {
result.add(queue.poll());
}
return result;
}
}
9.2 避免频繁扩容
对于数组实现的队列,预先分配足够的容量:
java复制// 如果能预估最大容量
Queue<String> queue = new ArrayDeque<>(expectedMaxSize);
9.3 对象池技术
对于高并发场景,可以使用对象池减少GC压力:
java复制public class ObjectPoolQueue<E> {
private final Queue<E> queue;
private final Supplier<E> creator;
private final Consumer<E> resetter;
public ObjectPoolQueue(Supplier<E> creator, Consumer<E> resetter) {
this.queue = new ConcurrentLinkedQueue<>();
this.creator = creator;
this.resetter = resetter;
}
public E borrow() {
E obj = queue.poll();
return obj != null ? obj : creator.get();
}
public void returnObject(E obj) {
resetter.accept(obj);
queue.offer(obj);
}
}
10. 队列在Java生态系统中的应用
10.1 线程池任务队列
Java的ThreadPoolExecutor使用BlockingQueue作为工作队列:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>() // 工作队列
);
10.2 Servlet异步处理
java复制@WebServlet("/async")
public class AsyncServlet extends HttpServlet {
private Queue<AsyncContext> clients = new ConcurrentLinkedQueue<>();
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext context = req.startAsync();
context.setTimeout(30000);
clients.add(context);
}
// 其他线程可以调用此方法通知所有客户端
public void notifyClients(String message) {
for (AsyncContext context : clients) {
try {
PrintWriter writer = context.getResponse().getWriter();
writer.write(message);
context.complete();
} catch (IOException e) {
// 处理异常
}
}
clients.clear();
}
}
10.3 事件驱动架构
java复制public class EventBus {
private final Queue<Event> eventQueue = new ConcurrentLinkedQueue<>();
private final Map<Class<?>, List<EventHandler>> handlers = new ConcurrentHashMap<>();
public void registerHandler(Class<?> eventType, EventHandler handler) {
handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()).add(handler);
}
public void post(Event event) {
eventQueue.offer(event);
}
public void start() {
new Thread(() -> {
while (true) {
Event event = eventQueue.poll();
if (event != null) {
List<EventHandler> eventHandlers = handlers.get(event.getClass());
if (eventHandlers != null) {
for (EventHandler handler : eventHandlers) {
handler.handle(event);
}
}
}
}
}).start();
}
}
在实际项目中,队列是构建高效、解耦系统的重要工具。理解各种队列实现的特性并根据场景选择合适的实现方式,是Java开发者必备的技能。