LinkedBlockingDeque是一个双向阻塞链表,线程安全的队列(与LinkedBlockingQueue类似,不过LinkedBlockingQueue是一个单向的阻塞链表),需要指定容量,如果开发者没有指定,那就默认为Integer.MAX_VALUE,它的内部也是维护了一个链表节点。
看看内部节点类
/** 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
* 前一个节点,如果它的前一个为null,那当前节点就是第一个
*/
Node<E> prev;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head
* - null, meaning there is no successor
* 下一个节点,如果下一个节点为null,那当前节点就是最后一个
*/
Node<E> next;
Node(E x) {
item = x;
}
}
node就是LinkedBlockingDeque内部维护的链表节点,有前一个节点和后一个节点,可以从节点两端进行出队入队操作
再看看定义的一些变量
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
* 第一个节点
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
* 最后一个节点
*/
transient Node<E> last;
/** Number of items in the deque
* 队列元素数量
* */
private transient int count;
/** Maximum number of items in the deque
* 队列元素最大数量
* */
private final int capacity;
/** Main lock guarding all access
* 队列操作锁
* */
final ReentrantLock lock = new ReentrantLock();
/** Condition for waiting takes
* 等待出队条件
* */
private final Condition notEmpty = lock.newCondition();
/** Condition for waiting puts
* 等待入队条件
* */
private final Condition notFull = lock.newCondition();
可以看到LinkedBlockingDeque跟LinkedBlockingQueue不同,它只使用了一把锁来控制出队入队操作,与ArrayBlockingQueue一样,说明同一时间只能有一个线程进行出队操作或者入队操作,两种操作是不可以同时进行的。
接下来看看构造方法
/**
* Creates a {@code LinkedBlockingDeque} with a capacity of
* {@link Integer#MAX_VALUE}.
*
* 默认指定链表大小
*/
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity.
*
* @param capacity the capacity of this deque
* @throws IllegalArgumentException if {@code capacity} is less than 1
* 指定链表大小
*/
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
}
/**
* Creates a {@code LinkedBlockingDeque} with a capacity of
* {@link Integer#MAX_VALUE}, initially containing the elements of
* the given collection, added in traversal order of the
* collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are null
* 默认指定链表大小,将传入的集合添加到链表
*/
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();
if (!linkLast(new Node<E>(e)))
throw new IllegalStateException("Deque full");
}
} finally {
lock.unlock();
}
}
构造方法与前面两篇文章中的数据结构基本类似,只不过这里第三个构造方法是将链表大小设置为Integer.MAX_VALUE.。
接下来看看第三个构造方法中的linkLast方法是如何将元素添加到链表尾部的。
/**
* Links node as last element, or returns false if full.
* 新增一个节点作为最后一个元素,也就是添加到链表的尾部,如果超过链表大小,返回false
*/
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
//将最后一个节点的引用赋值给l
Node<E> l = last;
//把当前最后一个节点赋值给这个新增节点的前一个节点,这也是双向链表的体现
node.prev = l;
//然后把这个新增节点赋值给最后一个节点,这样新增的节点就变成了最后一个节点
last = node;
//如果原来第一个节点为null,就把新增节点作为首节点
//反之,让新增节点成为原来最后一个节点的下一个节点
if (first == null)
first = node;
else
l.next = node;
//链表节点数加1
++count;
//唤醒等待的出队线程
notEmpty.signal();
return true;
}
其实最主要的就是把当前最后这个节点赋值给新增节点的前一个节点,然后新增节点赋值给当前最后节点的下一个节点,体现出双线链表。
既然有添加到链表尾部,那肯定也有添加到链表头部方法,看看
/**
* Links node as first element, or returns false if full.
* 新增一个节点作为第一个节点,也就是添加到链表头部,如果链表已满,返回false
*/
private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
//将当前第一个节点赋值给f
Node<E> f = first;
//将f作为新增节点的下一个节点,也就是把当前第一个节点作为新增节点的下一个节点
node.next = f;
//然后把新增节点作为第一个节点
first = node;
//如果最后一个节点为null,那就把新增节点也作为最后一个节点
//反之,就把新增节点作为第二个节点的前一个节点
if (last == null)
last = node;
else
f.prev = node;
//链表数量加一
++count;
//通知等待的出队线程
notEmpty.signal();
return true;
}
再看看获取链表节点方法
/**
* Removes and returns first element, or null if empty.
* 删除并返回头部元素,如果链表是空返回null
*/
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
//将当前头部节点赋值给f
Node<E> f = first;
if (f == null)
return null;
//将头部节点的下一个节点赋值给n
Node<E> n = f.next;
//将当前头部节点的元素赋值给item
E item = f.item;
//然后将当前头部节点的元素置为null
f.item = null;
//然后将当前头部节点赋值给下一个节点,上面已经置为null,方便回收
f.next = f; // help GC
//再将第二个节点赋值给当前节点,就这样删除了第一个节点
first = n;
//如果赋值后的当前节点为kong,就把最后一个节点也置为null
//反之,把赋值后的当前节点的前一个节点置为null
if (n == null)
last = null;
else
n.prev = null;
//链表数减一
--count;
//唤醒等待中的入队线程
notFull.signal();
return item;
}
/**
* Removes and returns last element, or null if empty.
* 删除尾部节点并返回尾部节点中的元素,如果链表是空的返回null
*/
private E unlinkLast() {
// assert lock.isHeldByCurrentThread();
//将最后一个节点赋值给l
Node<E> l = last;
if (l == null)
return null;
//将倒数第二个节点赋值给p
Node<E> p = l.prev;
//将最后一个节点的元素赋值给item
E item = l.item;
//再将最后一个节点的元素置为null
l.item = null;
//再将最后一个节点赋值给它前一个节点,方便回收最后一个节点
l.prev = l; // help GC
//再将p变成最后一个节点,这个p是前面赋值操作后的倒数第二个节点
last = p;
//如果p是空,将头部第一个节点也置为null
//反之,将这最后一个节点的下一个节点置为null
if (p == null)
first = null;
else
p.next = null;
//链表数减一
--count;
//通知等待中的入队线程
notFull.signal();
return item;
}
逻辑比较简单,接下来看看对外提供了哪些方法给开发者使用
节点入队
/**
* @throws IllegalStateException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* 添加头部节点,如果失败,直接抛出异常
*/
public void addFirst(E e) {
if (!offerFirst(e))
throw new IllegalStateException("Deque full");
}
/**
* @throws IllegalStateException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* 添加尾部节点,如果失败,直接抛出异常
*/
public void addLast(E e) {
if (!offerLast(e))
throw new IllegalStateException("Deque full");
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean offerFirst(E e) {
if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
//调用linkFirst方法添加头部节点,直接返回执行结果,可能成功可能失败
return linkFirst(node);
} finally {
lock.unlock();
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
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();
}
}
/**
* @throws NullPointerException {@inheritDoc}
* @throws InterruptedException {@inheritDoc}
* 新增头部节点
*/
public void putFirst(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 {
//调用linkFirst方法新增头部节点,如果这个方法返回false,说明链表已满,进入阻塞状态
while (!linkFirst(node))
notFull.await();
} finally {
//释放锁
lock.unlock();
}
}
/**
* @throws NullPointerException {@inheritDoc}
* @throws InterruptedException {@inheritDoc}
* 新增尾部节点
*/
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方法新增尾部节点,如果返回false,说明链表已满,进入阻塞状态
while (!linkLast(node))
notFull.await();
} finally {
lock.unlock();
}
}
节点出队
/**
* @throws NoSuchElementException {@inheritDoc}
* 移除头部节点,如果没有就抛出异常
*/
public E removeFirst() {
E x = pollFirst();
if (x == null) throw new NoSuchElementException();
return x;
}
/**
* @throws NoSuchElementException {@inheritDoc}
* 移除尾部节点,如果没有就抛出异常
*/
public E removeLast() {
E x = pollLast();
if (x == null) throw new NoSuchElementException();
return x;
}
//调用unlinkFirst方法获取头部元素,直接返回结果,可能为null
public E pollFirst() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkFirst();
} finally {
lock.unlock();
}
}
//调用unlinkLast方法获取尾部元素,直接返回结果,可能为null
public E pollLast() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkLast();
} finally {
lock.unlock();
}
}
//调用unlinkFirst方法获取头部元素,如果为空,这进入阻塞状态,直到获取成功
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();
}
}
//调用unlinkLast方法获取尾部元素,如果为空,这进入阻塞状态,直到获取成功
public E takeLast() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
while ( (x = unlinkLast()) == null)
notEmpty.await();
return x;
} finally {
lock.unlock();
}
}
其它方法与前面几篇文章分析的其它阻塞队列类似,这里就不在赘述了
到这里三个阻塞队列LinkedBlockingQueue、LinkedBlockingDeque、ArrayBlockingQueue就分析完了,可以总结出
1.LinkedBlockingDeque与ArrayBlockingQueue都是采用一把锁来控制队列的出队和入队操作,意味着同一时间只能进行出队操作或者入队操作;而LinkedBlockingQueue采用两把锁分别控制入队操作和出队操作,意味着同一时间可以有一个线程进行出队操作,一个线程进行入队操作或者反过来。
2.LinkedBlockingDeque是双向链表,即头尾两端都可以进行出队入队操作;而LinkedBlockingQueue是单向链表,只能从头部入队,尾部出队;ArrayBlockingQueue是保存出队索引和入队索引,采用循环数组进行出队入队操作
3.ArrayBlockingQueue和LinkedBlockingQueue都是采用先进先出的原则,而LinkedBlockingDeque可能对于头部或者尾部是先进先出原则。