概述
- 基于单链表实现的队列,队列最大节点容量可限定(默认容量为Interge.MAX_VALUE);
- 这个队列是阻塞队列,意味着当入队或出队操作不满足条件时或可抛异常、或可返回特殊值(通常null或false)、或可阻塞当前线程直至操作完成、或可在设定的超时时间到来时放弃操作;
- 队列元素FIFO(先进先出),也即链表尾部入队,头部出队;
- 只允许一个线程入队或出队,但是入队和出队操作可同时进行;
- 相比基于数组的阻塞队列(如ArrayBlockingQueue)拥有更高的吞吐量,但在大部分并发应用中可预测性能要差些;
- 不允许入队元素为null。
源码分析
继承关系和内部成员
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// 链表上的节点
static class Node<E> {
E item; // 节点存储put或offer对象
Node<E> next; // 下个节点引用
Node(E x) { item = x; }
}
// 记录队列上链接节点容量
private final int capacity;
// 记录链表节点总量
private final AtomicInteger count = new AtomicInteger();
// 记录链表头节点,节点上item常为null
transient Node<E> head;
// 记录链表尾节点,节点上next常为null
private transient Node<E> last;
// take或poll出队操作重入锁
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
// put或offer入队操作重入锁
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
...
}
总结:
- BlockingQueue的实现类
- 内部成员记录队列节点数量、队列头/尾节点、队列最多允许节点数量;
- 两把锁:一把入队锁,一把出队锁
构造函数
public LinkedBlockingQueue() { // 无参构造对象
this(Integer.MAX_VALUE);// 队列容量默认Integer.MAX_VALUE
}
public LinkedBlockingQueue(int capacity) { // 用指定容量构造对象
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null); // 注意节点item为null
}
public LinkedBlockingQueue(Collection<? extends E> c) { // 用集合对象初始化构造对象
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null) throw new NullPointerException();
if (n == capacity) throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e)); // 将集合元素一一入队
++n; // 统计入队节点数量
}
count.set(n); // 将最终的入队节点数量保存到count成员中
} finally {
putLock.unlock();
}
}
总结:
- 三个构造函数,第二个可限制链表节点数量,其他两个链表节点数量容量为Integer.MAX_VALUE;
- 前两个构造函数调用后,head和last被赋值,且相关对象如下图(第三个构造函数待入队源码分析后自然知晓):
put或offer入队操作
入队方法主要有如下几个方法:
public void put(E e) throws InterruptedException;
public boolean offer(E e);
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
拿最后一个方法的源码进行分析,其他两个类似:
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
// 计算入队操作阻塞时允许等待的超时时间
// 从后面代码看如果当前队列节点数量没满容量限制,timeout形参也是可以设0和负值的,此时退化成boolean offer(E e)方法
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count; // 获取链表队列节点数量
putLock.lockInterruptibly(); // 获取入队锁
try {
while (count.get() == capacity) { // 入队前链表节点数量已达限制
if (nanos <= 0L) return false; // 不超时等待入队失败
// 调用putLock关联的condition的awaitNanos()释放入队锁,线程休眠
nanos = notFull.awaitNanos(nanos); // 等待入队通知,超时时间到时还没通知入队该方法抛InterruptedException并退出
}
enqueue(new Node<E>(e)); // 新建节点并把该节点入队
c = count.getAndIncrement(); // count自增1,注意返回值c是count自增1前的count值
if (c + 1 < capacity) // 队列节点数还没达到限制值
notFull.signal(); // 这个有点意思,可能其他线程put或offer时await
} finally {
putLock.unlock();
}
if (c == 0) { // 入队前队列节点数为0,需要获取出队锁唤起出队休眠线程
// signalNotEmpty(); 源码调用该函数,为了方便直接注释,然后贴上该函数实际代码
// /* 下面是signalNotEmpty()的实际代码
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
// signalNotEmpty()的实际代码结束 */
}
return true;
}
offer方法中enqueue方法定义以及offer函数执行完后链表情况以及head、last指向如下图所示:
take或poll出队操作
出队方法主要有如下几个方法:
public E take() throws InterruptedException;
public E poll();
public E poll(long timeout, TimeUnit unit) throws InterruptedException
拿take方法进行源码分析:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count; // 获取当前count数量
final ReentrantLock takeLock = this.takeLock; // 获取出队锁
takeLock.lockInterruptibly();
try {
while (count.get() == 0) { // 链表为空释放出队锁,线程休眠
notEmpty.await(); // signalNotEmpty()函数会唤醒退出await()
}
x = dequeue(); // 出队
c = count.getAndDecrement(); // count自减1,并返回count自减1前的count值
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity) // 出队前队列节点已满限制
signalNotFull(); // 获取入队锁并唤醒等待的入队Condition
return x;
}
take方法中dequeue方法定义以及take方法执行完后链表以及head/last引用情况如下图所示:
后言
讲真,这个代码还是蛮简单的,之所以写它其实为了mark下,同时心里其实也惦记着写篇加强版的LinkedBlockingDeque简述,故而该文也算是个铺垫。
如果对ReentrantLock和对应的Condition相关函数有不懂,可以参考本人先前写的Lock框架简介。