一、概述
LinkedBlockingDeque是一个基于链表实现的有界的阻塞双端队列,该类和LinkedBlockingQueue的很像,只不过这个类是双端队列,队尾也可以出队,队头也可以入队,同样该队列的容量是可选的,并且元素不能为null。
二、源码
1. 继承结构
public class LinkedBlockingDeque<E>
extends AbstractQueue<E>
implements BlockingDeque<E>, java.io.Serializable {
LinkedBlockingDeque同样继承自AbstractQueue,并且实现了双端阻塞队列BlockingDeque接口,BlockingDeque前面说过,该接口扩展自BlockingQueue和Deque,另外该类支持序列化。
2. 属性
接下来,我们来看一下他的属性:
/**
* 对头节点
* 队头节点固定特性: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 队尾节点
* 队尾节点固定特性: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/** 队列中元素数量 */
private transient int count;
/** 队列的最大容量 */
private final int capacity;
/** 队列的主访问锁 */
final ReentrantLock lock = new ReentrantLock();
/** 队列的出队Condition */
private final Condition notEmpty = lock.newCondition();
/** 队列的入队Condition */
private final Condition notFull = lock.newCondition();
可以看到,和LinkedBlockingQueue不同的是,LinkedBlockingDeque只提供了一个访问锁。
3. 构造方法
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
// 初始化容量
this.capacity = capacity;
}
public LinkedBlockingDeque(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock lock = this.lock;
lock.lock(); // Never contended, but necessary for visibility
try {
for (E e : c) {
元素非空
if (e == null)
throw new NullPointerException();
// 调用了linkLast方法
if (!linkLast(new Node<E>(e)))
throw new IllegalStateException("Deque full");
}
} finally {
lock.unlock();
}
}
三个构造方法都很简单,第三个构造方法中调用了linkLast方法,将集合元素添加到队列尾部,这个方法后面再来说下。
4. 内部类
和LinkedBlockingQueue类似,LinkedBlockingDeque中也提供了一个内部类Node,用来保存元素。因为是双端队列,所以Node中除了保存元素外,还提供了指向前驱节点和后驱节点的指针,来简单看一下源码:
/** Doubly-linked list node class */
static final class Node<E> {
/**
* The item, or null if this node has been removed.
*/
E item;
/**
* One of:
* - the real predecessor Node
* - this Node, meaning the predecessor is tail
* - null, meaning there is no predecessor
*/
Node<E> prev;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head
* - null, meaning there is no successor
*/
Node<E> next;
Node(E x) {
item = x;
}
}
5. 方法
5.1 add方法
默认的 add(E) 方法,内部调用了addLast方法,表示从队尾插入,插入成功返回true,如果队列已满抛出异常:
public boolean add(E e) {
addLast(e);
return true;
}
然后我们来看下 addLast 方法,该方法又调用了 offerLast 方法:
public void addLast(E e) {
if (!offerLast(e))
throw new IllegalStateException("Deque full");
}
我们再来看下 offerLast 方法,该方法在队列满的时候不会抛出异常,而是返回false:
public boolean offerLast(E e) {
if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
return linkLast(node);
} finally {
lock.unlock();
}
}
offerLast 方法进行了加锁操作,并且调用了 linkLast 方法方法来实现,我们再来看下 linkLast 方法:
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
// 如果队列中元素数量超过容量限制,返回false
if (count >= capacity)
return false;
// 将新的节点添加至队尾
Node<E> l = last;
node.prev = l;
last = node;
// 如果队头为空,作为队头
if (first == null)
first = node;
else
// 否则,连接到队尾
l.next = node;
++count;
// 添加完成,唤醒notEmpty上等待的线程
notEmpty.signal();
return true;
}
可以看到,这几个方法最终都是通过 linkLast 来进行实现的。而addFirst
方法同样是调用offerFirst
方法,而offerFirst
又是通过调用linkFirst
方法来实现的,我们来看下这个 linkFirst方法:
private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
// 将新的节点添加到队头
Node<E> f = first;
node.next = f;
first = node;
if (last == null)
last = node;
else
f.prev = node;
++count;
notEmpty.signal();
return true;
}
这里也没什么好说的,就是将元素添加到队头位置,然后添加的时候判断下队尾节点是否为空(其实也就是队列是否为空),然后进行后续的操作。
5.2 offer方法
首先,默认的 offer(E) 方法内部调用了 offerLast 方法,而 offerLast 以及 offerFirst 方法的调用我们前面已经说过了,这里就不多说了:
public boolean offer(E e) {
return offerLast(e);
}
接下来,来看下超时的 offer(E,long,TimeUnit) 方法:
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
return offerLast(e, timeout, unit);
}
该方法又调用了超时的 offerLast(E,long,TimeUnit) 方法:
public boolean offerLast(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e);
// 计算超时时间
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 可中断锁
lock.lockInterruptibly();
try {
// 添加并判断是否队列已满
while (!linkLast(node)) {
// 判断是否超时,超时返回
if (nanos <= 0)
return false;
// 未超时,等待
nanos = notFull.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
这里添加的时候会进行循环判断,如果队列已满,并且已经超时,直接返回;如果没有超时,则等待对应的时间。而 offerFirst(E,long,TimeUnit) 逻辑和这个是相似的,只不过内部调用的是 linkFirst而已,就不多说了。
5.3 put方法
put方法内部是直接调用 putLast 来实现的,我们来简单看下 putLast的实现:
public void putLast(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 调用linkLast方法
while (!linkLast(node))
// 如果队列已满,阻塞
notFull.await();
} finally {
lock.unlock();
}
}
方法比较简单,内部调用 linkLast 方法,然后添加时判断是否队列已满,如果已满,notFull条件上的线程一直阻塞。而 putFirst 的方法实现相似,不多说了。
5.4 poll方法
poll 方法内部调用的是 pollFirst 方法,我们来简单看下:
public E pollFirst() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkFirst();
} finally {
lock.unlock();
}
}
而 pollFirst 方法则是调用 unlinkFirst 来实现移除元素功能,我们再来看下这个方法:
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
Node<E> f = first;
// 头节点为空(也就是队列为空),直接返回
if (f == null)
return null;
// 更新头节点
Node<E> n = f.next;
// 将头节点保存的数据返回
E item = f.item;
f.item = null;
f.next = f; // help GC
first = n;
if (n == null)
last = null;
else
n.prev = null;
--count;
// 唤醒对应线程
notFull.signal();
return item;
}
这个方法也比较简单,就是移除第一个节点,然后更新下头节点,中间会有一些简单的判断逻辑。当然还有一个对应的 unlinkLast 方法,操作相似,不多说了。
另外,这里顺便说下 remove 方法。这个方法比较简单,内部会调用 removeFirst 方法,而该方法内部又回调用 pollFirst 方法来实现,同样还有对应的 removeLast 则是调用 pollLast 方法。
5.5 take方法
take 方法也是相同的道理,内部调用的是 takeFirst 方法,我们来看下这个方法:
public E takeFirst() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
while ( (x = unlinkFirst()) == null)
notEmpty.await();
return x;
} finally {
lock.unlock();
}
}
这里,会通过 unlinkFirst 方法获取元素,获取元素的时候如果队列为空,notEmpty上的线程会进行阻塞。
5.6 peek方法
peek 方法也是同样的道理,内调调用 peekFirst,该方法也比较简单:
public E peekFirst() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果队列不为空,获取队列的头节点元素返回
return (first == null) ? null : first.item;
} finally {
lock.unlock();
}
}
另外,getFirst 方法是调用的 peekFirst,而 element 是调用的 getFirst 方法,所以这几个方法内部实现都是相同的。
其实该类的方法大部分都是通用的,都比较简单,这里就不多说了。如果用到哪个方法,到时候直接再看源码也可以。
三、总结
由于LinkedBlockingDeque比较简单,所以上面简单介绍下源码后,这里来简单总结下:
- LinkedBlockingDeque是一个基于链表实现的有界的双端阻塞队列;
- 该队列出队和入队都是使用的同一个锁;
- 该队列和其他常规阻塞队列一样,不能存储null值。