LinkedBlockingQueue
是一个可选是否有界的阻塞队列,由链表实现。
同其他队列一样,它遵循先进先出(FIFO)
规则,队头元素是队列中存储时间最长的元素,队尾是队列中存储时间最短的元素。元素从队头取出,从队尾插入。
该队列拥有比ArrayBlockingQueue
更高的吞吐量,但是在高并发情况下的可预测性能较差。
LinkedBlockingQueue
源码:
/** 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();
ArrayBlockingQueue
源码:
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
由源码可见,LinkedBlockingQueue
性能更高的一部分原因在于它使用的是分离锁,读和写是分离的,所以可以并发进行。
而ArrayBlockingQueue
使用的是公共锁,所以读时写阻塞,写时读阻塞。
该队列在创建时可以给定一个数值用于初始化容量,或默认使用Inteager.MAX
作为初始容量。
在向队列使用put()
方法添加元素时,若容量超过最大容量,则会进行等待。使用offer(E e, long timeout, TimeUnit unit)
添加元素时,若容量超限则会进行有限等待,若在给定时间内没有插入成功,则返回false
。
private final AtomicInteger count = new AtomicInteger();
底层实现使用AtomicInteger
来存储队列中现有元素数,因为它是线程安全的。
1. 入队
① put(E e)
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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();
}
与其他支持并发的Collection
子类一样,LinkedBlockingQueue
也不允许Null
值。
在添加元素时首先获得锁,然后检查是否容量已满,若已满则阻塞该线程。若未满则入队,之后获取当前count
再增加count
值。此时c
表示当前添加元素后的容量 - 1
。c + 1 < capacity
用于判断添加元素后是否还有空间,若有的话唤醒其他等待的生产者线程。解锁后判断是否c == 0
,由于c = 0
表示添加前容量为0
,则可知队列在该元素添加之前的状态是所有消费者都被阻塞,所以需要在这时候唤醒所有的消费者线程。
//入队源码
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
② take()
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
和put
方法类似。