You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于LinkedBlockingQueue源码中put方法的疑问

解析LinkedBlockingQueue的put方法核心逻辑

兄弟,我来给你拆解这段代码的核心逻辑——哪怕你贴的代码不完整,关键精髓已经能抓得住了,毕竟LinkedBlockingQueue的put方法是它并发设计的核心体现。

先铺垫下基础:LinkedBlockingQueue的核心特性

它是基于链表实现的有界阻塞队列,最关键的设计是用了两把分离的重入锁:putLock(负责入队操作同步)和takeLock(负责出队操作同步)。和ArrayBlockingQueue的单锁设计不同,这种分离锁允许入队和出队操作同时进行,大大提升了并发性能。

逐行解析你贴的代码片段

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // 注意:所有put/take等方法的惯例是预先将存储count的局部变量设为负值,除非赋值否则表示失败。
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        /* * 注意:即使count并非p...(代码未完整),它仍被用于等待守卫中。
  1. 非空校验
    if (e == null) throw new NullPointerException();
    这是Java集合的通用惯例,LinkedBlockingQueue不允许存储null元素,避免后续取元素时用null作为“队列为空”的判断标志(毕竟take方法返回null会有歧义)。

  2. 局部变量c的初始化
    int c = -1;
    注释里说的“预先设为负值”是团队约定:如果方法执行完c还是-1,说明入队操作失败(比如等待锁时被中断);正常执行的话,c会被赋值为入队前队列的元素数量,后续用来判断是否需要唤醒其他等待线程。

  3. 包装元素为Node节点
    Node<E> node = new Node<E>(e);
    LinkedBlockingQueue是链表结构,每个元素都会被包装成内部类Node的实例,包含元素本身和指向下一个节点的指针。

  4. 获取可中断锁
    putLock.lockInterruptibly();
    这里用lockInterruptibly()而不是普通的lock(),是为了支持中断响应:如果当前线程在等待获取锁的过程中被中断,会直接抛出InterruptedException,符合阻塞队列的中断语义(允许线程在等待时及时响应中断退出)。

  5. try块里的核心逻辑(补全你没贴的部分)
    虽然代码没写完,但这部分是put方法的灵魂,完整逻辑是:

    while (count.get() == capacity) {
        notFull.await();
    }
    enqueue(node); // 将节点加入链表尾部
    c = count.getAndIncrement(); // 原子更新计数,c是入队前的元素数
    if (c + 1 < capacity) {
        notFull.signal(); // 唤醒其他等待入队的线程(队列还没满)
    }
    
    • 等待队列有空闲空间:用while循环检查队列是否已满(count.get() == capacity),如果满了就通过notFull条件(属于putLock的Condition)进入等待,直到有线程取出元素后唤醒它。用while而不是if是为了防止虚假唤醒(操作系统层面的无理由唤醒),确保只有队列真的有空位时才继续执行。
    • 入队操作:enqueue(node)是线程安全的,因为此时已经持有putLock,不会有其他线程同时修改链表尾部。
    • 原子更新计数:count是AtomicInteger,getAndIncrement()保证多线程下计数的准确性,避免竞态条件。
    • 唤醒后续入队线程:如果入队后队列还没满(c+1 < capacity),就唤醒其他等待在notFull条件上的put线程,让它们继续入队。
  6. finally块与唤醒出队线程
    代码最后还有关键逻辑:

    } finally {
        putLock.unlock();
    }
    if (c == 0) {
        signalNotEmpty(); // 唤醒等待出队的线程
    }
    
    • 释放锁:finally块里必须释放putLock,避免锁泄漏。
    • 唤醒出队线程:如果c == 0,说明入队前队列是空的,现在队列里有元素了,需要唤醒等待在notEmpty条件(属于takeLock的Condition)上的take线程。这里调用signalNotEmpty()方法,内部会先获取takeLock,再调用notEmpty.signal()——因为Condition的signal操作必须持有对应的锁。

总结核心设计思路

LinkedBlockingQueue的put方法通过分离锁+Condition等待唤醒机制,实现了线程安全的阻塞入队,同时最大化并发性能:

  • 分离锁让入队和出队操作互不阻塞
  • Condition精准唤醒需要的线程(满时唤醒出队,空时唤醒入队)
  • 原子计数保证多线程下的计数准确
  • 可中断锁支持响应中断,符合阻塞队列的语义

内容的提问来源于stack exchange,提问作者vvsueprman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:01:56