关于Java LinkedBlockingQueue源码中锁与条件变量赋值逻辑的疑问
LinkedBlockingQueue中把全局锁/变量赋值给局部变量的原因
在阅读LinkedBlockingQueue源码时,你可能会注意到:明明takeLock、putLock、count这些成员变量已经是类中定义的final字段,每次在方法里使用时却还要先赋值给局部变量。这么做主要有以下几个原因:
1. 性能优化:降低字段访问开销
类的成员字段存储在堆内存中,每次访问都需要通过this引用间接寻址。而局部变量存储在栈上,甚至可以被JIT编译器缓存到CPU寄存器中,访问速度远快于堆上的成员字段。
对于lock()、unlock()这种高频调用的方法,把全局锁引用赋值给局部变量后,后续的锁操作都直接使用栈上的引用,能减少重复的堆内存寻址开销,累积下来对高并发场景下的性能提升有帮助。
2. 确保引用稳定性,避免潜在优化问题
虽然final字段在初始化完成后能保证对其他线程的可见性,但将其赋值给局部变量后,相当于在当前线程的栈中固定了这个引用。这样可以避免JIT编译器在优化时可能出现的指令重排序,确保整个方法执行期间,我们操作的始终是同一个锁/变量对象,不会出现意外的引用变化。
3. 编码约定与可读性
源码注释里也明确提到:Note: convention in all put/take/etc is to preset local var,这是LinkedBlockingQueue乃至JUC包中的一种编码惯例。把核心变量提前赋值给局部变量,能让代码风格保持一致,后续阅读代码时不用反复跳转查看类成员定义,逻辑更清晰,同时减少this.关键字的重复书写。
锁与条件变量初始化位置
/** The capacity bound, or Integer.MAX_VALUE if none */ private final int capacity; /** Current number of elements */ private final AtomicInteger count = new AtomicInteger(); /** * Head of linked list. * Invariant: head.item == null */ transient Node<E> head; /** * Tail of linked list. * Invariant: last.next == null */ private transient Node<E> last; /** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
赋值并使用的示例
// example 1 private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } } // example 2 public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { /* * Note that count is used in wait guard even though it is * not protected by lock. This works because count can * only decrease at this point (all other puts are shut * out by lock), and we (or some other waiting put) are * signalled if it ever changes from capacity. Similarly * for all other uses of count in other wait guards. */ while (count.get() == capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); }
内容的提问来源于stack exchange,提问作者PopCornnnn
相关产品推荐
相关产品推荐

