关于ArrayBlockingQueue.take方法多线程锁状态的疑问
我在阅读ArrayBlockingQueue的take()方法源码时遇到了一个困惑。我原本以为当两个线程并发调用take()方法时,只有一个线程能成功获取锁,另一个会在lock.lockInterruptibly();处等待锁。
take()方法源码如下:
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } }
但当我对这两个线程做线程dump时,发现它们居然都成功获取了锁,并且都卡在notEmpty.await();处等待(此时队列是空的)。线程dump信息如下:
"test-thread-18" #6357 daemon prio=5 os_prio=0 tid=0x00007f8f54543000 nid=0x58ef waiting on condition [0x00007f901bc70000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00007f93ae695410> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)"test-thread-17" #6356 daemon prio=5 os_prio=0 tid=0x00007f8f54542000 nid=0x58ee waiting on condition [0x00007f901beb9000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00007f93ae695410> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
请问为什么两个不同的线程可以同时获取同一把锁?我的理解哪里出错了?
这其实是你对Condition.await()方法的机制理解有偏差啦,核心点在于:当线程调用Condition.await()时,会自动释放持有的锁,让其他线程有机会获取锁进入临界区。
具体到你的场景:
- 第一个线程调用
take(),成功获取ReentrantLock,然后发现队列是空的,调用notEmpty.await()。这时候,该线程会把锁释放掉,然后进入条件队列等待被唤醒。 - 第二个线程此时就能顺利获取到同一把
ReentrantLock,同样检查队列是空的,也调用notEmpty.await(),同样释放锁,进入条件队列等待。
这样就出现了dump里看到的两个线程都在await()处等待的情况——它们都曾经获取过锁,但在调用await后都主动释放了,所以不会出现锁竞争的问题。
等后续有线程往队列里放入元素时,会调用notEmpty.signal()或signalAll(),唤醒条件队列里的线程,被唤醒的线程会重新去竞争锁,拿到锁之后才能继续执行dequeue()操作。
总结一下:Condition.await()的设计就是让线程在等待条件满足时,释放锁让其他线程可以进入,避免持有锁等待造成的资源浪费,这是并发包里条件变量的核心机制之一。
内容的提问来源于stack exchange,提问作者Expressway Retrograding

