LinkedBlockingQueue详解

1. 概述

LinkedBlockingQueue 是 Java 并发包(java.util.concurrent)中提供的一个可选有界 的阻塞队列实现,其底层基于单向链表 组织元素,严格遵循 FIFO(先进先出) 顺序。它是阻塞队列系列中应用最为广泛的实现之一,尤其在生产者-消费者模型和高并发场景中扮演着关键角色。

核心特点一览

特性 说明
数据结构 单向链表,每个元素被包装为 Node 节点,通过 next 指针串联
有界/无界 可选有界 。无参构造器默认容量为 Integer.MAX_VALUE,可视为"无界"
锁机制 双锁分离putLock 管理入队操作,takeLock 管理出队操作,大幅提升并发吞吐量
公平性 不支持公平参数。双锁设计下难以实现公平调度,实践中饥饿概率较低
内存分配 动态分配节点,每次插入创建新 Node 对象;使用哨兵节点(dummy head)简化边界判断
容量计数 使用 AtomicInteger 维护元素个数,保证跨锁可见性,实现轻量级竞争
所属包 java.util.concurrent

典型应用场景

  • 生产者-消费者模型:解耦生产线程和消费线程,利用阻塞特性实现流量削峰。
  • 线程池任务队列Executors.newFixedThreadPool()Executors.newSingleThreadExecutor() 默认使用 LinkedBlockingQueue 作为任务缓冲队列(无界)。
  • 消息中间件缓冲层:如日志异步处理、事件分发器等。

与 ArrayBlockingQueue 的主要区别(先睹为快)

对比维度 LinkedBlockingQueue ArrayBlockingQueue
锁结构 双锁分离(putLock + takeLock 单锁(一把 ReentrantLock 管理入队和出队)
内存结构 链表,动态分配节点 数组,预分配连续内存
有界性默认值 无参构造容量 = Integer.MAX_VALUE(近似无界) 必须显式指定容量,无默认无界构造器
公平性 不支持 支持公平锁(fair 参数)
内存占用 较高,每个元素需额外存储 Node 对象头和 next 指针 较低,仅数组引用
GC 压力 较大,节点频繁创建与回收 较小,数组元素覆盖
吞吐量特征 高并发下表现更优,生产者和消费者可并发操作 竞争激烈时性能下降明显

本文将深入 JDK 8 源码,逐层剖析 LinkedBlockingQueue 的设计思想与实现细节,并与 ArrayBlockingQueue 进行对比,帮助你建立全面的阻塞队列知识体系。


2. 核心方法说明

以下是 LinkedBlockingQueue 主要 API 的速查表,涵盖了构造、插入、移除、检查、批量操作等核心方法。

方法 参数 返回值 阻塞行为 异常
LinkedBlockingQueue() 构造器,容量 = Integer.MAX_VALUE
LinkedBlockingQueue(int capacity) capacity:队列容量(必须 > 0) 构造器 IllegalArgumentExceptioncapacity <= 0
LinkedBlockingQueue(Collection<? extends E> c) 初始集合 构造器,容量 = Integer.MAX_VALUE,添加集合元素 NullPointerException 若集合或元素为 null
put(E e) e:元素 void 队列满时阻塞,直到有空间 InterruptedExceptionNullPointerException
offer(E e) e:元素 boolean:成功 true,队列满返回 false 不阻塞 NullPointerException
offer(E e, long timeout, TimeUnit unit) 元素、超时时间、时间单位 boolean:成功 true,超时后仍满返回 false 等待指定时间 InterruptedExceptionNullPointerException
take() E:队首元素 队列空时阻塞,直到有元素 InterruptedException
poll() E:队首元素,空返回 null 不阻塞
poll(long timeout, TimeUnit unit) 超时时间、时间单位 E:元素,超时后仍空返回 null 等待指定时间 InterruptedException
peek() E:队首元素(不移除),空返回 null 不阻塞
size() int:当前元素个数(AtomicInteger,弱一致性)
remainingCapacity() int:剩余容量(容量 - 当前个数)
drainTo(Collection<? super E> c) 目标集合 int:转移的元素数量 无(但分批持锁) NullPointerException 若集合为 null
drainTo(Collection<? super E> c, int maxElements) 目标集合、最大转移数 int:实际转移数 无(分批持锁) NullPointerException

注意size() 返回的是 AtomicInteger 的瞬时值,由于双锁设计,count 可能在调用后立即变化,因此具有弱一致性remainingCapacity() 在无界队列下总是返回 Integer.MAX_VALUE - count,意义有限。


3. 核心原理与源码分析(基于 JDK 8)

3.1 数据结构与核心字段

LinkedBlockingQueue 内部通过一个静态内部类 Node 构建单向链表,同时维护了两个独立的锁和对应的条件队列。

java 复制代码
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 链表节点定义
    static class Node<E> {
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }

    // 容量限制(final,一经设定不可变)
    private final int capacity;

    // 当前元素个数(AtomicInteger 保证跨锁可见性和原子更新)
    private final AtomicInteger count = new AtomicInteger();

    // 链表头指针(始终指向哨兵节点,其 item 为 null)
    transient Node<E> head;

    // 链表尾指针(指向最后一个节点,其 next 为 null)
    private transient Node<E> last;

    // 消费者锁(出队操作使用)
    private final ReentrantLock takeLock = new ReentrantLock();

    // 非空条件(消费者等待)
    private final Condition notEmpty = takeLock.newCondition();

    // 生产者锁(入队操作使用)
    private final ReentrantLock putLock = new ReentrantLock();

    // 非满条件(生产者等待)
    private final Condition notFull = putLock.newCondition();
}

字段详解

  • head :始终指向一个 哨兵节点 (dummy node),其 item == null。第一个有效元素存放在 head.next 中。这一设计极大简化了边界条件判断------出队时无需检查 head 是否为 null,只需获取 head.next 即可。
  • last :指向链表最后一个节点。入队时直接追加到 last.next,然后更新 last
  • count :使用 AtomicInteger 而非 volatile int。因为入队和出队使用不同的锁,count 的更新可能在两把锁的保护下分别进行,需要保证原子性和跨锁可见性。AtomicInteger 完美满足这一需求,且 CAS 操作比锁开销更小。
  • takeLock / putLock :两把独立的 ReentrantLock,分别保护出队和入队操作。这是 LinkedBlockingQueue 高并发性能的核心支柱。
  • notEmpty / notFull :分别绑定在 takeLockputLock 上的条件队列,用于线程间的等待/唤醒协调。

3.2 Node 内部类设计

java 复制代码
static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

节点设计极简,仅包含数据 item 和后继指针 next没有前驱指针 ,因为队列仅支持 FIFO 的单向遍历,无需反向操作。这也意味着 LinkedBlockingQueueremove(Object) 操作需要从头遍历链表,时间复杂度 O(n)。

3.3 构造器源码分析

① 无参构造器

java 复制代码
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

直接调用有参构造器,传入最大容量 Integer.MAX_VALUE(约 21 亿),实际上近似于"无界"。

② 指定容量构造器

java 复制代码
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null); // 创建哨兵节点
}

这里创建了哨兵节点,并让 headlast 都指向它。此时队列为空,head.next == null

③ 从集合初始化的构造器

java 复制代码
public LinkedBlockingQueue(Collection<? extends E> c) {
    this(Integer.MAX_VALUE);
    final ReentrantLock putLock = this.putLock;
    putLock.lock(); // 锁定,防止并发干扰
    try {
        int n = 0;
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            if (n == capacity)
                throw new IllegalStateException("Queue full");
            enqueue(new Node<E>(e));
            ++n;
        }
        count.set(n);
    } finally {
        putLock.unlock();
    }
}

持有 putLock 批量入队,避免并发问题。注意容量默认为 Integer.MAX_VALUE,但若集合元素数量超过该值会抛异常(实际几乎不可能)。

3.4 put / take 核心流程:双锁分离与级联唤醒

这是 LinkedBlockingQueue 最精彩的部分。我们通过源码逐行剖析。

put(E e) 实现

java 复制代码
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        // 1. 若队列已满,则在 notFull 条件上等待
        while (count.get() == capacity) {
            notFull.await();
        }
        // 2. 入队操作
        enqueue(node);
        // 3. 获取入队前的元素个数,并将 count 加 1
        c = count.getAndIncrement();
        // 4. 如果入队后仍有空间,则唤醒其他等待的生产者
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // 5. 若入队前队列为空(c == 0),则唤醒可能等待的消费者
    if (c == 0)
        signalNotEmpty();
}

take() 实现

java 复制代码
public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        // 1. 若队列为空,则在 notEmpty 条件上等待
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 2. 出队操作
        x = dequeue();
        // 3. 获取出队前的元素个数,并将 count 减 1
        c = count.getAndDecrement();
        // 4. 如果出队后仍有元素,则唤醒其他等待的消费者
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 5. 若出队前队列已满(c == capacity),则唤醒可能等待的生产者
    if (c == capacity)
        signalNotFull();
    return x;
}

级联唤醒机制分析

两个方法在释放锁之后,都额外进行了一次条件唤醒:

  • put 中:若 c == 0(入队前队列为空),则调用 signalNotEmpty() 唤醒消费者。
  • take 中:若 c == capacity(出队前队列已满),则调用 signalNotFull() 唤醒生产者。

为什么要在释放锁之后唤醒?

为了减少锁竞争。signalNotEmpty() 需要获取 takeLock,如果还在持有 putLock 的状态下执行,就会形成锁嵌套 ,不仅降低并发度,还可能增加死锁风险。JDK 的设计者将唤醒动作推迟到释放 putLock 之后,使得生产者线程尽早释放锁,消费者线程可立即竞争 takeLock,从而提升吞吐量。

为什么只在边界条件时唤醒?

如果每次 put 都唤醒消费者,会带来大量无效唤醒(例如队列未满时消费者本来就不会阻塞)。仅在 c == 0 时唤醒,表明队列从空变为非空 ,此时才可能有消费者正在等待。同理,仅在 c == capacity 时唤醒生产者。这种精确唤醒策略大幅减少了不必要的上下文切换。

3.5 offer / poll 非阻塞版本

offer(E e)poll()put/take 的非阻塞版本。

java 复制代码
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false; // 快速失败,无需加锁
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

注意在加锁前先进行了一次 count.get() == capacity快速检查 。这是一种无锁优化,能减少不必要的加锁开销,但即便检查通过,加锁后仍需再次验证(因为并发环境下 count 可能已被改变)。若加锁后发现队列已满,则返回 false。成功时的唤醒逻辑与 put 一致。

poll() 实现完全对称。

3.6 超时版本 offer / poll

利用 Condition.awaitNanos(long nanosTimeout) 实现限时等待。

java 复制代码
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    // ...
    long nanos = unit.toNanos(timeout);
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(node);
        // ... 唤醒逻辑同 offer
    } finally {
        putLock.unlock();
    }
    // ...
}

awaitNanos() 返回剩余等待时间(纳秒),若超时则返回 ≤ 0。循环检查保证了即使被虚假唤醒也能正确重试。

3.7 链表操作详解:enqueue 与 dequeue

enqueue(Node node)

java 复制代码
private void enqueue(Node<E> node) {
    // 经典的单链表尾插法
    last = last.next = node;
}

等价于:

  1. last.next = node;
  2. last = node;

由于有哨兵节点,初始时 head == last,第一次入队时 head.next = nodelast 指向新节点。

dequeue()

java 复制代码
private E dequeue() {
    Node<E> h = head;          // 哨兵节点
    Node<E> first = h.next;    // 真正的第一个元素
    h.next = h;                // help GC
    head = first;              // first 成为新的哨兵
    E x = first.item;
    first.item = null;         // 新哨兵的 item 为 null
    return x;
}

关键细节 h.next = h 的作用:将旧哨兵节点的 next 指向自身,形成一个自引用环 。这样一来,旧哨兵节点不再被任何外部引用可达(head 已指向新哨兵),且其内部也没有指向其他有效节点的引用,有助于 GC 快速回收。尽管现代 JVM 的 GC 已足够智能,但这种显式辅助仍是良好实践。

3.8 双锁设计精髓总结

  1. 并发度倍增 :生产者和消费者可以同时操作队列(一个在尾部插入,一个在头部移除),互不干扰。只有在操作 count 时存在轻量级的 CAS 竞争。
  2. AtomicInteger 作为桥梁count 是连接两把锁的共享变量,使用 CAS 保证原子性,同时其 volatile 语义(AtomicInteger 内部使用 volatile 修饰 value)确保跨锁的可见性。
  3. 精确唤醒 :仅在队列从空变非空从满变非满时跨锁唤醒对方线程,避免无效竞争。
  4. 无公平性:由于两把锁独立,实现全局公平调度异常复杂,且对于大多数高并发场景,吞吐量优先的非公平策略更为合适。

3.9 容量上限风险警示

capacity = Integer.MAX_VALUE 时,put 操作几乎永远不会阻塞(除非系统内存耗尽)。这在生产者速率持续高于消费者时,会导致队列无限增长,最终引发 OutOfMemoryError生产环境中,务必根据实际内存和业务承载能力设置合理的有界容量


4. 必要流程的 Mermaid 图

4.1 类图

展示 LinkedBlockingQueue 的核心字段和方法,并与 ArrayBlockingQueue 进行对比。

classDiagram class LinkedBlockingQueue~E~ { -int capacity -AtomicInteger count -Node~E~ head -Node~E~ last -ReentrantLock takeLock -Condition notEmpty -ReentrantLock putLock -Condition notFull +LinkedBlockingQueue() +LinkedBlockingQueue(int) +put(E e) +offer(E e) +offer(E e, long, TimeUnit) +take() E +poll() E +poll(long, TimeUnit) E +peek() E +size() int +drainTo(Collection) } class Node~E~ { -E item -Node~E~ next +Node(E x) } LinkedBlockingQueue *-- Node class ArrayBlockingQueue~E~ { -final Object[] items -int takeIndex -int putIndex -int count -ReentrantLock lock -Condition notEmpty -Condition notFull +ArrayBlockingQueue(int) +ArrayBlockingQueue(int, boolean) +put(E e) +take() E ... } note for LinkedBlockingQueue "双锁分离设计\n无公平参数" note for ArrayBlockingQueue "单锁设计\n支持公平/非公平"

对比描述LinkedBlockingQueue 使用链表和两把锁,字段包含 head/last 指针和独立的锁与条件;而 ArrayBlockingQueue 使用数组和单把 lock,包含 takeIndex/putIndex 等环形数组索引。

4.2 链表结构图(包含三个元素)

graph LR subgraph "队列状态(容量=3,count=3)" head["head
(哨兵节点)
item=null"] --> node1["Node1
item=A
next→"] node1 --> node2["Node2
item=B
next→"] node2 --> node3["Node3
item=C
next=null"] last["last"] --> node3 end
  • 哨兵节点始终位于队首,head 指向它。
  • 第一个有效元素在 head.next
  • last 指向最后一个节点,其 nextnull

4.3 双锁工作示意图

graph TB subgraph "并发操作示意" Producer1["生产者线程1
put(E)"] -->|竞争| putLock["putLock
(入队锁)"] Producer2["生产者线程2
put(E)"] -->|竞争| putLock putLock -->|允许一个线程| TailOps["操作 last
enqueue"] Consumer1["消费者线程1
take()"] -->|竞争| takeLock["takeLock
(出队锁)"] Consumer2["消费者线程2
take()"] -->|竞争| takeLock takeLock -->|允许一个线程| HeadOps["操作 head
dequeue"] TailOps -.->|更新 count| AtomicCount["AtomicInteger count
(CAS 操作)"] HeadOps -.->|更新 count| AtomicCount end style putLock fill:#f9f,stroke:#333,stroke-width:2px style takeLock fill:#bbf,stroke:#333,stroke-width:2px
  • 生产者和消费者分别持有不同的锁,可同时进行入队和出队操作。
  • 只有在更新 count 时才会发生轻量级的 CAS 竞争(非阻塞)。

4.4 非阻塞 offer/poll 快速路径流程图

为清晰展示两个非阻塞方法的执行路径,分别绘制 offer(E e)poll() 流程图。

① offer(E e) 流程图

graph TD Start[开始 offer] --> FastCheck{count == capacity ?
无锁快速检查} FastCheck -- 是 --> ReturnFalse1[返回 false] FastCheck -- 否 --> LockPut[获取 putLock] LockPut --> CheckAgain{count < capacity ?} CheckAgain -- 否 --> UnlockFalse[释放 putLock
返回 false] CheckAgain -- 是 --> Enqueue[执行 enqueue 入队] Enqueue --> GetCount[ c = count.getAndIncrement ] GetCount --> CheckSignalSelf{ c + 1 < capacity ? } CheckSignalSelf -- 是 --> SignalNotFull[notFull.signal
唤醒其他生产者] CheckSignalSelf -- 否 --> UnlockPut[释放 putLock] SignalNotFull --> UnlockPut UnlockPut --> CheckEmptyBoundary{ c == 0 ? } CheckEmptyBoundary -- 是 --> SignalNotEmpty[signalNotEmpty
唤醒消费者] CheckEmptyBoundary -- 否 --> ReturnTrue[返回 true] SignalNotEmpty --> ReturnTrue

② poll() 流程图

graph TD Start[开始 poll] --> FastCheck{count == 0 ?
无锁快速检查} FastCheck -- 是 --> ReturnNull1[返回 null] FastCheck -- 否 --> LockTake[获取 takeLock] LockTake --> CheckAgain{count > 0 ?} CheckAgain -- 否 --> UnlockNull[释放 takeLock
返回 null] CheckAgain -- 是 --> Dequeue[执行 dequeue 出队] Dequeue --> GetCount[ c = count.getAndDecrement ] GetCount --> CheckSignalSelf{ c > 1 ? } CheckSignalSelf -- 是 --> SignalNotEmptySelf[notEmpty.signal
唤醒其他消费者] CheckSignalSelf -- 否 --> UnlockTake[释放 takeLock] SignalNotEmptySelf --> UnlockTake UnlockTake --> CheckFullBoundary{ c == capacity ? } CheckFullBoundary -- 是 --> SignalNotFull[signalNotFull
唤醒生产者] CheckFullBoundary -- 否 --> ReturnItem[返回元素] SignalNotFull --> ReturnItem

流程图详细文字描述

offer 流程描述

  1. 快速无锁检查 :首先不获取锁检查 count 是否等于 capacity,若满则立即返回 false(此优化减少加锁开销)。
  2. 获取 putLock:如果可能不满,获取生产者锁。
  3. 双重检查:加锁后再次检查容量,防止并发导致的状态变化。
  4. 入队与计数更新 :执行 enqueue,并通过 getAndIncrement 获取入队前的计数值 c
  5. 条件唤醒(生产者侧) :若入队后队列仍有余量(c+1 < capacity),则唤醒一个等待在 notFull 上的其他生产者。
  6. 释放生产者锁
  7. 边界唤醒(消费者侧) :若入队前队列为空(c == 0),说明可能有消费者正在等待,跨锁调用 signalNotEmpty 唤醒消费者。
  8. 返回 true

poll 流程描述

  1. 快速无锁检查 :检查 count 是否为 0,若空则返回 null
  2. 获取 takeLock:若可能非空,获取消费者锁。
  3. 双重检查:加锁后再次检查队列是否为空。
  4. 出队与计数更新 :执行 dequeue,通过 getAndDecrement 获取出队前的计数值 c
  5. 条件唤醒(消费者侧) :若出队后仍有元素(c > 1),唤醒其他等待的消费者。
  6. 释放消费者锁
  7. 边界唤醒(生产者侧) :若出队前队列已满(c == capacity),说明可能有生产者正在等待空间,跨锁调用 signalNotFull 唤醒生产者。
  8. 返回被移除的元素

4.5 阻塞 put/take 完整交互时序图

场景一:队列空,take 先阻塞

sequenceDiagram participant Consumer as 消费者线程 participant takeLock as takeLock participant notEmpty as notEmpty participant count as count(AtomicInteger) participant Producer as 生产者线程 participant putLock as putLock Consumer->>takeLock: lockInterruptibly() takeLock-->>Consumer: 获得锁 Consumer->>count: get() == 0? count-->>Consumer: true (队列空) Consumer->>notEmpty: await() note over Consumer: 释放 takeLock,线程阻塞 Producer->>putLock: lockInterruptibly() putLock-->>Producer: 获得锁 Producer->>Producer: enqueue(node) Producer->>count: c = getAndIncrement() count-->>Producer: c=0 (原队列为空) Producer->>putLock: unlock() alt c == 0 (队列由空变非空) Producer->>takeLock: lock() (signalNotEmpty内部) takeLock-->>Producer: 获得锁 Producer->>notEmpty: signal() Producer->>takeLock: unlock() end notEmpty-->>Consumer: 被唤醒 Consumer->>takeLock: 重新获取锁 takeLock-->>Consumer: 获得锁 Consumer->>Consumer: dequeue() Consumer->>count: getAndDecrement() Consumer->>takeLock: unlock() Consumer-->>Consumer: 返回元素

流程描述:

  1. 初始状态 :队列为空,count.get() == 0。此时一个消费者线程(Consumer)调用 take() 准备获取元素。

  2. 消费者阻塞

    • Consumer 调用 takeLock.lockInterruptibly() 获取消费者锁,成功后进入临界区。
    • 检查 count.get() == 0,确认队列为空。
    • 调用 notEmpty.await()。此操作会原子地 释放 takeLock 并将当前线程挂起到 notEmpty 条件队列上,消费者进入阻塞状态。
  3. 生产者入队

    • 随后,一个生产者线程(Producer)调用 put(e) 插入元素。
    • Producer 获取 putLock(与 takeLock 无关,因此可以独立进行),成功后进入临界区。
    • 由于队列有空余(容量未满),直接执行 enqueue(node),将新节点追加到链表尾部。
    • 执行 c = count.getAndIncrement(),获取入队前的元素个数。因为队列原本为空,所以 c == 0
    • 入队后队列未满(通常如此,除非容量为 1),Producer 会调用 notFull.signal() 唤醒其他可能等待的生产者(本例中无)。
    • 释放 putLock
  4. 边界唤醒消费者

    • Producer 在释放 putLock 后,检查到 c == 0(入队前队列为空),表明队列由空变为非空,此时极有可能有消费者在等待。
    • 调用 signalNotEmpty() 方法。该方法内部会获取 takeLock,然后调用 notEmpty.signal() 唤醒一个等待的消费者,最后释放 takeLock
    • 注意:此唤醒操作发生在释放 putLock 之后,避免了锁嵌套,提高了并发效率。
  5. 消费者被唤醒并完成出队

    • 被唤醒的 Consumerawait() 调用中返回,并自动重新获取 takeLockawait 方法在返回前会重新竞争锁)。
    • 重新获得锁后,Consumerwhile (count.get() == 0) 循环中退出(因为此时 count 已变为 1)。
    • 执行 dequeue() 从链表头部移除元素。
    • 执行 c = count.getAndDecrement(),获取出队前的元素个数(此时 c == 1)。
    • 由于出队后队列仍可能非空(c > 1 时),Consumer 会调用 notEmpty.signal() 唤醒其他可能等待的消费者,形成传播唤醒
    • 释放 takeLock
  6. 边界检查

    • Consumer 在释放锁后检查 c == capacity(出队前队列是否已满)。在本场景中 c == 1 且容量大于 1,因此条件不成立,不会调用 signalNotFull()
    • 最终 Consumer 返回获取的元素,take() 调用结束。

通过上述步骤,LinkedBlockingQueue 利用精确的边界唤醒 (只在 c == 0 时唤醒消费者,只在 c == capacity 时唤醒生产者)避免了无效的信号风暴,并结合双锁分离实现了高并发下的高效协作。

场景二:队列满,put 先阻塞

sequenceDiagram participant Producer1 as 生产者线程1 participant Producer2 as 生产者线程2 participant putLock as putLock participant notFull as notFull participant count as count(AtomicInteger) participant Consumer as 消费者线程 participant takeLock as takeLock Note over Producer1,Consumer: 初始状态:队列已满(count == capacity) %% 第一个生产者尝试入队,因队列满而阻塞 Producer1->>putLock: lockInterruptibly() putLock-->>Producer1: 获得锁 Producer1->>count: get() == capacity? count-->>Producer1: true (队列满) Producer1->>notFull: await() note over Producer1: 释放 putLock,线程阻塞 %% 第二个生产者同样尝试入队,也阻塞 Producer2->>putLock: lockInterruptibly() putLock-->>Producer2: 获得锁 Producer2->>count: get() == capacity? count-->>Producer2: true (队列满) Producer2->>notFull: await() note over Producer2: 释放 putLock,线程阻塞 %% 消费者执行出队,使队列出现空位 Consumer->>takeLock: lockInterruptibly() takeLock-->>Consumer: 获得锁 Consumer->>count: get() == 0? count-->>Consumer: false (队列非空) Consumer->>Consumer: dequeue() Consumer->>count: c = getAndDecrement() count-->>Consumer: c = capacity (原队列满) Consumer->>takeLock: unlock() %% 因为出队前队列满,需要唤醒一个生产者 alt c == capacity (队列由满变非满) Consumer->>putLock: lock() (signalNotFull内部) putLock-->>Consumer: 获得锁 Consumer->>notFull: signal() note over notFull: 唤醒等待队列中的第一个生产者(Producer1) Consumer->>putLock: unlock() end %% 被唤醒的生产者1重新竞争 putLock 并完成入队 notFull-->>Producer1: 被唤醒 Producer1->>putLock: 重新获取锁 putLock-->>Producer1: 获得锁 Producer1->>Producer1: enqueue(node) Producer1->>count: c = getAndIncrement() count-->>Producer1: c = capacity - 1 opt 入队后队列仍有余量 (c+1 < capacity) Producer1->>notFull: signal() note over notFull: 唤醒下一个生产者(Producer2) end Producer1->>putLock: unlock() Producer1-->>Producer1: put() 返回 %% 如果 Producer2 被唤醒,将重复上述过程 opt Producer2 被唤醒 notFull-->>Producer2: 被唤醒 Producer2->>putLock: 重新获取锁 putLock-->>Producer2: 获得锁 Producer2->>Producer2: enqueue(node) Producer2->>count: getAndIncrement() Producer2->>putLock: unlock() Producer2-->>Producer2: put() 返回 end

流程描述

  1. 初始状态 :队列已满,count.get() == capacity。此时有多个生产者线程(示例中为 Producer1Producer2)尝试调用 put() 插入元素。
  2. 生产者阻塞
    • Producer1 获取 putLock 后检查队列状态,发现已满,于是调用 notFull.await() 释放锁并阻塞。
    • Producer2 同样获取 putLock 并因队列满而阻塞在 notFull 条件上。
    • 此时 notFull 的条件等待队列中可能存在多个生产者线程。
  3. 消费者出队触发唤醒
    • 一个消费者线程(Consumer)调用 take() 获取 takeLock,执行 dequeue() 移除一个元素。
    • 出队前计数值 c = count.getAndDecrement() 等于 capacity,表明队列由满变为非满
    • 消费者在释放 takeLock 后,调用 signalNotFull() 跨锁唤醒一个等待在 notFull 上的生产者(通常是等待最久的那个,具体取决于 AQS 的唤醒策略,非公平锁下不保证严格 FIFO)。
  4. 生产者被唤醒并完成入队
    • Producer1await() 返回后,会重新竞争 putLock。获得锁后继续执行入队操作(enqueue)。
    • 入队后 c = count.getAndIncrement() 返回入队前的计数值(此时应为 capacity - 1)。
    • 若入队后队列仍未满(c + 1 < capacity),Producer1 会调用 notFull.signal() 唤醒下一个阻塞的生产者(例如 Producer2),形成传播唤醒,直到队列再次满或没有等待者为止。
  5. 级联唤醒的优雅性:这种设计确保了只要队列中有空间,就会尽可能多地激活生产者,避免线程在不必要时继续阻塞,同时避免了生产者侧的条件队列"饿死"。

4.6 超时 offer 的等待-超时恢复流程

stateDiagram-v2 [*] --> 计算超时时间 计算超时时间 --> 获取putLock可中断锁 获取putLock可中断锁 --> 检查队列状态 state 检查队列状态 { [*] --> 队列满? 队列满? --> 是: nanos>0? 是 --> 等待: awaitNanos(nanos) 等待 --> 更新nanos 更新nanos --> 队列满? 队列满? --> 否: 入队 队列满? --> 超时: nanos<=0 超时 --> 返回false } 入队 --> 更新count 更新count --> 条件唤醒 条件唤醒 --> 释放putLock 释放putLock --> 跨锁唤醒? 跨锁唤醒? --> 是: signalNotEmpty 跨锁唤醒? --> 否: 返回true 是 --> 返回true

流程说明:

  1. 参数预处理与超时计算

    方法首先检查元素是否为 null,若是则抛出 NullPointerException。然后将用户传入的时间单位转换为纳秒:
    long nanos = unit.toNanos(timeout);

  2. 获取生产者锁(可中断)

    调用 putLock.lockInterruptibly() 获取锁。若线程在等待锁期间被中断,则抛出 InterruptedException

  3. 循环检查队列状态与超时

    进入 try 块后,使用 while (count.get() == capacity) 循环持续判断队列是否已满:

    • 若队列未满,跳出循环,执行入队操作。

    • 若队列已满,则检查剩余等待时间 nanos

      • nanos <= 0,表示已超时,直接返回 false
      • 否则,调用 nanos = notFull.awaitNanos(nanos) 进入限时等待状态。
  4. awaitNanos 的行为
    awaitNanos 会使当前线程释放 putLock 并挂起,直到以下四种情况之一发生:

    • 被唤醒 (通过 signalsignalAll)。
    • 超时(到达指定等待时间)。
    • 被中断 (抛出 InterruptedException)。
    • 虚假唤醒 (spurious wakeup)。
      方法返回值是剩余等待时间timeout - 实际等待时长),单位纳秒。若返回值 ≤ 0,表明已超时。
  5. 超时后重试逻辑

    由于可能存在虚假唤醒,线程被唤醒后必须再次检查 队列状态。循环将使用更新后的 nanos 继续判断:

    • 若此时队列仍满且 nanos > 0,则继续等待剩余时间。
    • nanos <= 0,则退出循环,最终在 finally 中释放锁并返回 false
  6. 成功入队的后续处理

    若队列出现空位(无论是因为被消费者唤醒还是刚好超时前有空位),则跳出循环,执行:

    • enqueue(node) 将元素加入链表尾部。
    • c = count.getAndIncrement() 获取入队前计数。
    • 若入队后队列仍未满(c + 1 < capacity),则调用 notFull.signal() 唤醒其他可能等待的生产者。
    • 释放 putLock
  7. 边界唤醒消费者

    在释放锁后,若入队前队列为空(c == 0),则调用 signalNotEmpty() 跨锁唤醒消费者,确保因队列空而阻塞的消费者能够及时获取新元素。

  8. 返回结果

    最终方法返回 true,表示元素成功入队。

与 ArrayBlockingQueue 对比
ArrayBlockingQueueoffer 超时版本同样使用 awaitNanos,但由于其单锁设计,入队和出队竞争同一把锁,超时等待期间其他线程无法进行任何操作。而 LinkedBlockingQueue 中,超时等待仅阻塞持有 putLock 的生产者,消费者仍可并发地通过 takeLock 出队,从而减少了超时等待线程对整体吞吐量的影响。

4.7 drainTo 批量消费内部循环流程图

graph TD subgraph "drainTo(Collection c, int maxElements)" D1[开始] --> D2[获取 takeLock] D2 --> D3{n = min maxElements, count} D3 --> D4[循环 i=0 to n-1] D4 --> D5[dequeue 出队] D5 --> D6[添加到集合 c] D6 --> D7{i < n-1?} D7 -->|是| D4 D7 -->|否| D8["count.getAndAdd(-n)"] D8 --> D9{原 count == capacity?} D9 -->|是| D10["signalNotFull()"] D9 -->|否| D11[释放 takeLock] D10 --> D11 D11 --> D12[返回 n] end

流程说明:

  1. 参数校验

    首先检查目标集合 c 是否为 null,若为 null 则抛出 NullPointerException。同时检查 c 是否为当前队列本身(会导致死循环),若是则抛出 IllegalArgumentException

    maxElements <= 0,则直接返回 0(不转移任何元素)。

  2. 获取消费者锁

    调用 takeLock.lock() 获取消费者锁。注意,此处使用不可中断的 lock(),因为批量转移过程通常较快,中断支持意义不大,且保持代码简洁。

  3. 确定实际转移数量

    计算本次最多可转移的元素个数:
    int n = Math.min(maxElements, count.get());

    其中 count.get() 是当前队列中的元素总数。由于已持有 takeLock,此值在后续循环中不会因消费者并发而增加(但生产者可能入队,不过入队操作不修改 head 区域,因此不影响遍历)。

  4. 循环执行出队并添加到目标集合

    使用 for 循环执行 n 次:

    • 调用 dequeue() 方法移除队首元素。注意:dequeue 内部会更新 head 指针,并处理哨兵节点的自引用以帮助 GC。
    • 将返回的元素 E x 通过 c.add(x) 添加到目标集合。
    • 若在添加过程中目标集合抛出异常(如集合不支持添加元素、类型不匹配等),则已转移的元素将保留在集合中,但循环会中断,并抛出相应异常。同时,队列中已移除的元素不可恢复,这符合 drainTo 的"尽力而为"语义。
  5. 原子更新队列计数

    循环结束后,调用 count.getAndAdd(-n) 原子地将 count 减去实际转移的元素数量 n

    注意:即使循环中途因异常退出,n 也仅代表实际成功转移的数量(可通过循环计数器精确控制),因此 count 的更新是准确的。

  6. 唤醒可能等待的生产者

    在释放锁之前,检查转移前的队列是否已满:

    • 若转移前 count == capacity(即队列满),说明生产者可能正阻塞在 notFull 条件上。由于已经移除了 n 个元素,队列由满变为非满,因此需要调用 signalNotFull() 唤醒一个或多个生产者(具体唤醒数量取决于 n 的大小,但 JDK 8 中简单调用一次 signal 即可,因为生产者会在获取锁后重新检查容量,如有余量会级联唤醒其他生产者)。
  7. 释放消费者锁

    finally 块中释放 takeLock

  8. 返回转移数量

    方法返回实际转移的元素个数 n

与 ArrayBlockingQueue 的差异分析

  • 锁持有范围LinkedBlockingQueuedrainTo 仅持有 takeLock,因此在批量转移过程中,生产者可以继续入队 (除非队列满需等待),极大地降低了对生产者线程的阻塞影响。而 ArrayBlockingQueuedrainTo 持有单把全局锁,转移期间生产者完全无法入队。
  • 批量操作效率 :两者都通过单次加锁完成多个元素的移除,相比循环 poll 大幅减少了锁获取和释放的开销。但 LinkedBlockingQueue 由于双锁分离,其 drainTo 对整体并发度的扰动更小,尤其适合消费者需要批量处理而生产者又要求低延迟的场景。

5. 实际应用场景与代码举例(JDK 8 兼容)

以下所有示例代码均基于 JDK 8,包含完整的 importmain 方法,可直接复制编译运行。

5.1 基础生产者-消费者模型

java 复制代码
import java.util.concurrent.LinkedBlockingQueue;

public class BasicProducerConsumer {
    public static void main(String[] args) {
        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(5);

        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    String msg = "Msg-" + i;
                    queue.put(msg);
                    System.out.println("生产: " + msg + " [队列大小: " + queue.size() + "]");
                    Thread.sleep(200); // 模拟生产耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer");

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    String msg = queue.take();
                    System.out.println("消费: " + msg + " [队列大小: " + queue.size() + "]");
                    Thread.sleep(500); // 模拟消费耗时(慢于生产,演示阻塞效果)
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer");

        producer.start();
        consumer.start();
    }
}

5.2 使用超时 offer/poll

java 复制代码
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class TimeoutOfferPollExample {
    public static void main(String[] args) {
        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(2);

        // 生产者:尝试1秒内插入,超时则放弃
        Thread producer = new Thread(() -> {
            String[] items = {"A", "B", "C", "D"};
            for (String item : items) {
                try {
                    boolean success = queue.offer(item, 1, TimeUnit.SECONDS);
                    if (success) {
                        System.out.println("成功插入: " + item);
                    } else {
                        System.out.println("插入失败(队列满): " + item);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });

        // 消费者:慢消费,500ms取一次,超时则退出
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 3; i++) {
                    String item = queue.poll(500, TimeUnit.MILLISECONDS);
                    if (item != null) {
                        System.out.println("消费: " + item);
                        Thread.sleep(1500); // 慢消费导致队列很快满
                    } else {
                        System.out.println("消费超时,退出");
                        break;
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
    }
}

5.3 使用 drainTo 批量消费

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

public class DrainToExample {
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        
        // 快速生产100个元素
        for (int i = 1; i <= 100; i++) {
            queue.put(i);
        }
        System.out.println("初始队列大小: " + queue.size());

        // 批量消费,每次最多取30个
        List<Integer> batch = new ArrayList<>();
        int totalDrained = 0;
        while (!queue.isEmpty()) {
            int drained = queue.drainTo(batch, 30);
            totalDrained += drained;
            System.out.printf("本批取出 %d 个元素,内容: %s%n", drained, batch);
            batch.clear();
        }
        System.out.println("总共取出: " + totalDrained);
    }
}

5.4 结合线程池(任务队列与拒绝策略)

java 复制代码
import java.util.concurrent.*;

public class ThreadPoolWithLBQ {
    public static void main(String[] args) {
        // 核心线程1,最大线程2,队列容量10
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1, 2, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:抛异常
        );

        // 提交20个任务,超出 maxPoolSize + queueCapacity = 2 + 10 = 12
        for (int i = 1; i <= 20; i++) {
            final int taskId = i;
            try {
                executor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
                    try {
                        Thread.sleep(2000); // 模拟任务执行
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
                System.out.println("任务 " + taskId + " 提交成功");
            } catch (RejectedExecutionException e) {
                System.out.println("任务 " + taskId + " 被拒绝: " + e.getMessage());
            }
        }

        executor.shutdown();
    }
}

与 ArrayBlockingQueue 对比 :若将队列替换为 ArrayBlockingQueue,由于单锁设计,当任务提交和线程池工作线程并发访问队列时,锁竞争会更激烈。但在本例中由于任务执行耗时较长,差异不明显。高并发短任务场景下 LinkedBlockingQueue 优势更突出。

5.5 演示无界队列风险

java 复制代码
import java.util.concurrent.LinkedBlockingQueue;

public class UnboundedQueueRisk {
    public static void main(String[] args) throws InterruptedException {
        // 无参构造,容量为 Integer.MAX_VALUE
        LinkedBlockingQueue<byte[]> queue = new LinkedBlockingQueue<>();

        // 生产者疯狂生产,每个元素占用1MB
        Thread producer = new Thread(() -> {
            int count = 0;
            try {
                while (true) {
                    queue.put(new byte[1024 * 1024]); // 1MB
                    count++;
                    if (count % 100 == 0) {
                        System.out.println("已生产: " + count + " MB");
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        producer.start();

        // 消费者几乎不消费(注释掉或极慢)
        // 观察内存使用情况(可通过 jvisualvm 或 top 查看)
        Thread.sleep(10000);
        System.out.println("队列当前大小: " + queue.size());
        // 最终可能抛出 OutOfMemoryError
    }
}

运行此代码前请确保 JVM 堆内存设置较小(如 -Xmx256m),以便更快观察到 OOM。


6. 吞吐量与性能分析

6.1 双锁分离设计对吞吐量的提升

ArrayBlockingQueue 中,puttake 操作竞争同一把锁。当并发度升高时,锁竞争成为瓶颈,CPU 大量时间消耗在上下文切换和自旋等待上。

LinkedBlockingQueue双锁分离 允许生产者和消费者完全并行执行:

  • 生产者线程在持有 putLock 时操作链表尾部,与操作头部的消费者互不影响。
  • 仅当需要更新共享变量 count 时,才通过 AtomicInteger 的 CAS 操作进行轻量级同步。

这种设计使得 LinkedBlockingQueue高并发、生产消费速率相近 的场景下,吞吐量显著高于 ArrayBlockingQueue。许多基准测试表明,在 10+ 线程并发时,LinkedBlockingQueue 的吞吐量可达到 ArrayBlockingQueue 的 2~3 倍。

6.2 链表内存开销

每个元素必须封装为 Node 对象,带来额外内存开销:

  • 对象头:在 64 位 JVM 开启压缩指针时约 12 字节。
  • next 引用:4 字节(压缩)或 8 字节。
  • 再加上 item 引用(4/8 字节)。

平均每个元素额外占用约 24~32 字节。对于百万级元素,内存压力不可忽视。同时,频繁创建和丢弃 Node 对象会增加 GC 压力,可能导致 YGC 频率升高或 FGC 停顿。

6.3 无界 vs 有界

  • 无界队列capacity = Integer.MAX_VALUE):生产者永不阻塞(除非内存耗尽),系统失去背压(backpressure)机制,容易导致内存溢出或请求积压超时。
  • 有界队列:提供天然的流量控制,但需要合理设置容量。容量过小则频繁阻塞/唤醒,过大则内存风险高。建议根据实际生产消费速率、JVM 内存和业务容忍度综合评估。

6.4 公平性

LinkedBlockingQueue 未提供公平锁参数,主要原因在于双锁设计下实现全局公平调度难度极高(需协调两把锁的等待队列)。实际应用中,双锁已经分散了竞争,线程饥饿的概率远低于单锁队列。

6.5 性能调优建议

  1. 合理设置容量:根据最大可容忍内存和期望的排队长度设定。可通过监控队列大小的变化趋势动态调整。
  2. 优先使用 offer/poll 的超时版本:避免线程无限期阻塞,增强系统弹性。
  3. 善用 drainTo 批量操作:在消费者端,批量取出元素能减少加锁次数,提升处理效率。尤其适用于日志批量写入、数据库批量插入等场景。
  4. 监控队列大小和内存占用:对于无界队列务必监控,防止内存泄漏。

7. 注意事项与常见陷阱

陷阱 具体原因与后果
无界容量可能导致内存溢出 默认构造器 capacity = Integer.MAX_VALUE。若消费者速度长期低于生产者,队列节点只增不减,最终堆内存耗尽抛出 OutOfMemoryError
size() 方法是弱一致性 返回 AtomicInteger 的瞬时值。由于 count 在锁外更新,且入队/出队并发执行,size() 结果可能不反映调用后立即的准确数量。典型场景:刚刚判空后可能立即有元素入队。
remainingCapacity() 的瞬间性 返回值在下一秒可能已变化,不能用于精确的流控决策。尤其对于无界队列,返回值总是 Integer.MAX_VALUE - count,意义不大。
链表节点内部自引用帮助 GC dequeue() 中的 h.next = h 使旧哨兵节点自引用,切断其与有效数据的联系。普通使用者无需关心,但了解此细节有助于理解为什么不会发生内存泄漏。
双锁下的信号唤醒时机必须精确 生产者仅在 c == 0 时唤醒消费者,消费者仅在 c == capacity 时唤醒生产者。错误实现(如每次操作都唤醒)会导致大量无效唤醒,浪费 CPU。JDK 实现正确,自定义队列时需注意。
禁止 null 元素 put/offer 会检查元素非空,否则抛出 NullPointerException。原因:null 被用作 poll() 的特殊返回值,表示队列空。
迭代器弱一致性 iterator() 返回的迭代器是弱一致性的,不反映创建后的修改,且不支持 remove() 操作(会抛出 UnsupportedOperationException)。
drainTo 不会持有整个队列的锁 drainTo 仅持有 takeLock,生产者仍可并发入队。但注意若目标集合操作耗时很长,仍会阻塞其他消费者。

8. 与 ArrayBlockingQueue 的详细对比总结

对比维度 LinkedBlockingQueue ArrayBlockingQueue
数据结构 单向链表 循环数组
有界性 可选有界(默认 Integer.MAX_VALUE 必须显式指定容量,强制有界
锁数量 双锁(putLock / takeLock 单锁(lock
公平性支持 不支持 支持(fair 参数)
内存分配 动态分配节点,每次插入新建 Node 预分配连续数组,元素引用覆盖
内存占用 较高(每个元素 + Node 对象开销) 较低(仅数组引用)
GC 压力 较大,节点频繁创建回收 较小,数组复用
吞吐量特征 高并发下更高,生产消费可并行 中等,高竞争时性能下降明显
典型应用场景 高并发生产者-消费者、线程池默认队列、对公平性无要求场景 固定大小缓冲区、要求公平调度、内存敏感或低 GC 场景
drainTo 锁持有 仅持有 takeLock,生产者可并发入队 全程持有单锁,阻塞所有操作
迭代器 弱一致性,不支持 remove 弱一致性,支持 remove

9. 总结与学习指引

核心特点回顾

  • 链表结构:动态扩展,使用哨兵节点简化边界处理。
  • 可选有界:无参构造为"无界",需警惕内存溢出风险。
  • 双锁分离:入队锁和出队锁独立,极大提升并发吞吐量。
  • 无公平参数:以吞吐量为首要设计目标,非公平调度。
  • 精确唤醒:仅在边界条件跨锁唤醒,减少无效竞争。

使用建议

  • 推荐场景
    • 高并发的生产者-消费者模型。
    • Executors.newFixedThreadPool() 等默认线程池的任务队列(需注意无界风险)。
    • 对内存占用和 GC 不敏感,或元素数量可控的场景。
  • 不推荐场景
    • 内存资源紧张或对 GC 停顿敏感的系统(优先考虑 ArrayBlockingQueue)。
    • 需要严格公平调度的场景(考虑 ArrayBlockingQueue 公平模式)。
    • 需要精确控制内存占用的嵌入式环境。

学习路径指引

本文是阻塞队列系列的第二篇。通过深入分析 LinkedBlockingQueue,你已经掌握了链表结构、双锁分离、条件队列协作等核心并发设计模式。下一篇文章我们将探讨零容量的同步队列------SynchronousQueue,它将揭示如何在没有内部容量的情况下实现高效的生产者-消费者握手,以及其独特的"栈"与"队列"两种公平模式实现。敬请期待!


相关推荐
敖正炀1 小时前
ArrayBlockingQueue深度解析
java
敖正炀2 小时前
SynchronousQueue 详解
java
wuyikeer2 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
努力努力再努力wz2 小时前
【Linux网络系列】深入理解 I/O 多路复用:从 select 痛点到 poll 高并发服务器落地,基于 Poll、智能指针与非阻塞 I/O与线程池手写一个高性能 HTTP 服务器!(附源码)
java·linux·运维·服务器·c语言·c++·python
努力努力再努力wz2 小时前
【Linux网络系列】万字硬核解析网络层核心:IP协议到IP 分片重组、NAT技术及 RIP/OSPF 动态路由全景
java·linux·运维·服务器·数据结构·c++·python
LaLaLa_OvO2 小时前
mybatis 引用静态常量
java·mybatis
Han_han9192 小时前
常用API:
java·开发语言
小锋java12342 小时前
LangChain4j 来了,Java AI智能体开发再次起飞。。。
java·人工智能·后端
敖正炀2 小时前
BlockingQueue 详解
java