LinkedBlockingQueue
LinkedBlockingQueue 是 Java 并发包中基于链表实现的线程安全阻塞队列,适用于生产者-消费者模式中的任务队列实现。
一、核心特性
数据结构
- 基于单向链表的 FIFO 队列实现,内部通过 head 和 last 节点维护队列结构;
- 默认容量为 Integer.MAX_VALUE(理论无界),但可手动指定固定容量实现有界队列。
- 内部使用单向链表来存储元素,这使得它在内存使用上相对灵活,因为它的容量可以是有限的也可以是无限的。
并发控制
- 使用分离锁机制:putLock 控制入队操作,takeLock 控制出队操作,提升并发吞吐量;
- 默认采用非公平锁策略,初始化时可指定公平锁(但可能降低性能)。
阻塞策略
- 队列满时,入队操作阻塞(put() 方法)或超时返回(offer(e, timeout, unit));
- 队列空时,出队操作阻塞(take() 方法)或超时返回(poll(timeout, unit))。
- 当尝试从空队列中获取元素时,获取操作会被阻塞直到队列变得非空;同样地,如果试图将元素放入已满的有界队列中,放入操作也会被阻塞直到空间可用。
二、常用方法对比
操作类型 | 抛出异常 | 返回特殊值 | 阻塞 | 超时阻塞 |
插入元素 | add(e) | offer(e) | put(e) | offer(e, t, u) |
移除元素 | remove() | poll() | take() | poll(t, u) |
检查元素 | element() | peek() | - | - |
- 队列满时:add() 抛 IllegalStateException,offer() 返回 false,put() 无限阻塞;
- 队列空时:remove() 抛 NoSuchElementException,poll() 返回 null,take() 无限阻塞。
三、源码实现要点
入队逻辑
- 通过 enqueue(node) 方法将新节点链接至链表尾部,更新 last 指针,并通过 count.getAndIncrement() 原子更新队列长度;
- 若插入后队列未满,通过 signalNotEmpty() 唤醒可能阻塞的消费者线程。
出队逻辑
- 通过 dequeue() 方法移除链表头节点,更新 head 指针,并通过 count.getAndDecrement() 原子减少队列长度;
- 若移除后队列非空,通过 signalNotFull() 唤醒可能阻塞的生产者线程。
四、适用场景
高并发生产者-消费者模型
- 分离锁设计减少锁竞争,适合生产消费速率差异大的场景;
- 默认无界队列适用于任务量不可预知但需快速响应的场景,但需警惕内存溢出风险。
- LinkedBlockingQueue 经常被用作工作队列,在执行器框架(如 ThreadPoolExecutor)中作为任务提交和执行之间的桥梁。
性能对比
- 相比 ArrayBlockingQueue,链表结构更灵活,但连续内存访问效率稍低;
- 相比 PriorityBlockingQueue,严格保证 FIFO 顺序,不支持优先级排序。
五、典型示例
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100); // 有界队列,容量100
// 生产者线程
new Thread(() -> {
try {
queue.put(1); // 阻塞插入
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
Integer value = queue.take(); // 阻塞获取
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();