队列经常用来连接生产数据和处理数据的线程。选择队列之前,先想清楚两件事:没有数据时要不要等待,数据积压时允许占用多少空间。
非阻塞队列
ConcurrentLinkedQueue 是线程安全的无界队列,常用操作是 offer() 和 poll()。没有元素时,poll() 返回 null,不会等生产者放入数据。
JDK 8 的实现以链表和 CAS 为基础,入队、出队通过更新节点链接和值完成,不依靠一把锁把所有访问串起来。它适合需要立即返回的队列操作,但如果消费者在空队列上无限循环 poll(),仍然会消耗 CPU。
不要先判断 isEmpty() 再认为 poll() 一定有值。判断之后,其他消费者可能已经取走元素,直接检查 poll() 的返回值即可。
BlockingQueue 的几组方法
| 操作 | 抛异常 | 返回特殊值 | 一直等待 | 限时等待 |
|---|---|---|---|---|
| 插入 | add | offer | put | offer(value, time, unit) |
| 取出 | remove | poll | take | poll(time, unit) |
| 查看队头 | element | peek | — | — |
这里的“等待”主要指满或空时的行为。put() 和 take() 可以响应中断,使用时需要处理 InterruptedException。队列不接受 null,这也使 poll() 返回 null 的含义明确。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class QueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);
System.out.println(queue.offer(10));
System.out.println(queue.offer(20));
System.out.println(queue.take());
System.out.println(queue.poll());
}
}输出 true、false、10、null。容量为 1 时,第二次 offer 失败,程序可以决定拒绝、稍后重试或交给其他处理方式。
几种实现
ArrayBlockingQueue 使用固定大小的数组,创建时确定容量。JDK 8 实现用一把锁配合非空、未满两个条件。
LinkedBlockingQueue 使用链表,可以指定容量;不指定时上限是 Integer.MAX_VALUE,实际使用仍受内存限制。它分别协调插入和取出,并用原子计数连接两侧的状态,不意味着完全没有锁。
SynchronousQueue 不保存元素,每次交付都需要生产者和消费者配对。它的容量不是“特别小”,而是没有用于积压任务的内部存储。
PriorityBlockingQueue 按优先级出队,逻辑上无界;相同优先级不保证 FIFO。DelayQueue 只允许取出延迟已经到期的元素,空或队头尚未到期时 take 会等待。
需要双端操作可考虑 LinkedBlockingDeque。LinkedTransferQueue.transfer() 则能等待元素被消费者接收;这和普通 put() 只保证元素成功进入队列不同。
容量也是控制手段
有界队列能把积压限制在一定范围内,但它不会自动决定满了怎么办。选择等待还是拒绝,要结合请求能等待多久、任务是否可以丢弃。
还要注意,把对象放进队列后又在外面修改它,可能与消费者发生数据竞争。队列安全地交付了引用,并不替你保护交付之后的所有修改。