CSDN博客地址:https://blog.csdn.net/liuyu973971883
文章来源:转载,原文地址:https://blog.csdn.net/weixin_41622183/article/details/88870755,感谢这位老哥的辛勤付出,写的非常棒,各位看完别忘了给这位老哥点个赞啊。如有侵权,请联系删除。
上一篇文章我们介绍了BlockingQueue接口,既然BlockingDeque接口继承自 BlockingQueue 接口,那我们大致知道有哪些功能了。接下来看看 BlockingQueue 和 BlockingDeque 接口对比,BlockingDeque 提供了哪些功能呢?
提供功能 | BlockingQueue | BlockingDeque |
---|---|---|
头部入队 | × | √ |
尾部入队 | √ | √ |
头接出队 | √ | √ |
尾部部出队 | × | √ |
如上图,我们看到的从 BlockingDeque 接口比BlockingQueue 接口多了两个功能,分别是头部入队和尾部出队,放入如下:
提供功能 | 方法名 |
---|---|
头部入队 | putFirst()、offerFirst()、addFirst() |
尾部出队 | addLast()、offerLast()、putLast() |
//头结点
transient Node<E> first;
//尾结点
transient Node<E> last;
//队列中个数
private transient int count;
//队列长度,可以使用构造注入,如未设定,默认为无界队列
private final int capacity;
//显示锁
final ReentrantLock lock = new ReentrantLock();
//消费队列(队列为空时,无法消费,线程阻塞)
private final Condition notEmpty = lock.newCondition();
//生产队列(队列满时,无法入队,线程阻塞)
private final Condition notFull = lock.newCondition();
入队操作,该方法是阻塞的
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 {
//入队操作,如果队列满了,则线程阻塞
while (!linkFirst(node))
notFull.await();
} finally {
lock.unlock();
}
}
入队操作,该方法是非阻塞的
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 {
//返回是否入队成功 true 为成功,false 为失败
return linkFirst(node);
} finally {
//解锁,显示锁一定要解锁。
lock.unlock();
}
}
private boolean linkFirst(Node<E> node) {
/*
* 判断当前队列中数量是否超过队列允许的边界
* 如果使用默认构造,长度为 Integer.MAX_VALUE
* 这长度等于是无界了
*/
if (count >= capacity)
return false;
//获取头节点对象
Node<E> f = first;
//获取头结点下一个对象
node.next = f;
//将下一个对象设为头节点
first = node;
//判断尾节点是否为空对象
if (last == null)
//尾节点也指向我们设置的节点
last = node;
else
//将原来的第一个节点的前置节点
//指向我们新入队的节点
f.prev = node;
//队列长度加 1
++count;
//唤醒堵塞的消费线程(task()操作)
notEmpty.signal();
return true;
}
下面是我们两个 Node(data,pre,next 构成一个节点)
由于我们是从队首入队。这时,新节点的 next 会断掉指向空的节点,next 指向原来的 head 节点对象。 你可以理解为小弟取代自己的原来老大拿到第一把交椅。
这时候,旧节点指将自己的前置节点指向新入队的节点。可以理解:新老大上任,宽宏大量,让旧老大叫自己一声 “老大" 好就行了。
出队方法,该方法是阻塞的
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();
}
}
出队方法,该方法是非阻塞的
public E pollLast() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//出队操作 出队成功返回出队的值,失败则为空
return unlinkLast();
} finally {
lock.unlock();
}
}
private E unlinkLast() {
//拿到最后一个节点
Node<E> l = last;
//如果最后一个节点是空,则返回空
if (l == null)
return null;
//拿到最后一个节点的前置节点
Node<E> p = l.prev;
//拿到返回值
E item = l.item;
//item 结节点,设空表示已经删除
l.item = null;
//将前置节点指向字节,
l.prev = l;
//将前置节点设置为尾节点
last = p;
//判断最后一个 last 是否为空
if (p == null)
first = null;
else
p.next = null;
//减少队列中数量
--count;
//唤醒入队的阻塞线程
notFull.signal();
return item;
}
准备三个 Node(data,pre,next 构成一个节点) 节点对象:
首先先断掉指向上一个节点的前置节点,然后将前置节点指向自己(前置节点指向自己,可以帮助 GC 回收已经无用的节点)
最后,将 last 对象指向将 next 指向空的对象。OK,游戏结束。
LinkedBlockingDeque 作为一种阻塞双端队列,在 BlockingQueue 的基础上增加队首队尾的出入队方法。在日常开发中需要选择相关业务来选择阻塞队列。当然,打死也不能用无界队列,这玩意不可控,一旦失控就 GG。最后祝大家学习进步,工作愉快。谢谢