关于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...(代码未完整),它仍被用于等待守卫中。
非空校验
if (e == null) throw new NullPointerException();
这是Java集合的通用惯例,LinkedBlockingQueue不允许存储null元素,避免后续取元素时用null作为“队列为空”的判断标志(毕竟take方法返回null会有歧义)。局部变量c的初始化
int c = -1;
注释里说的“预先设为负值”是团队约定:如果方法执行完c还是-1,说明入队操作失败(比如等待锁时被中断);正常执行的话,c会被赋值为入队前队列的元素数量,后续用来判断是否需要唤醒其他等待线程。包装元素为Node节点
Node<E> node = new Node<E>(e);
LinkedBlockingQueue是链表结构,每个元素都会被包装成内部类Node的实例,包含元素本身和指向下一个节点的指针。获取可中断锁
putLock.lockInterruptibly();
这里用lockInterruptibly()而不是普通的lock(),是为了支持中断响应:如果当前线程在等待获取锁的过程中被中断,会直接抛出InterruptedException,符合阻塞队列的中断语义(允许线程在等待时及时响应中断退出)。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线程,让它们继续入队。
- 等待队列有空闲空间:用
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

