阻塞队列之六:LinkedBlockingDeque
一、LinkedBlockingDeque简介
java6增加了两种容器类型,Deque和BlockingDeque,它们分别对Queue和BlockingQueue进行了扩展。
Deque是一个双端队列,deque(双端队列) 是 "Double Ended Queue" 的缩写。因此,双端队列是一个你可以从任意一端插入或者抽取元素的队列。实现了在队列头和队列尾的高效插入和移除。
BlockingDeque 类是一个双端队列,在不能够插入元素时,它将阻塞住试图插入元素的线程;在不能够抽取元素时,它将阻塞住试图抽取的线程。
正如阻塞队列使用与生产者-消费者模式,双端队列同样适用于另一种相关模式,即工作密取。在生产者-消费者设计中,所有消费者有一个共享的工作队列,而在工作密取设计中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其它消费者双端队列末尾秘密地获取工作。密取工作模式比传统的生产者-消费者模式具有更高的可伸缩性,这是因为工作者线程不会在单个共享的任务队列上发生竞争。在大多数时候,它们都只是访问自己的双端队列,从而极大地减少了竞争。当工作者线程需要访问另一个队列时,它会从队列的尾部而不是头部获取工作,因此进一步降低了队列上的竞争程度。
LinkedBlockingDeque是双向链表实现的双向并发阻塞队列。该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除);并且,该阻塞队列是支持线程安全。
此外,LinkedBlockingDeque还是可选容量的(防止过度膨胀),即可以指定队列的容量。如果不指定,默认容量大小等于Integer.MAX_VALUE。
BlockingDeque 的使用
在线程既是一个队列的生产者又是这个队列的消费者的时候可以使用到 BlockingDeque。如果生产者线程需要在队列的两端都可以插入数据,消费者线程需要在队列的两端都可以移除数据,这个时候也可以使用 BlockingDeque。BlockingDeque 图解:
一个 BlockingDeque - 线程在双端队列的两端都可以插入和提取元素。
一个线程生产元素,并把它们插入到队列的任意一端。如果双端队列已满,插入线程将被阻塞,直到一个移除线程从该队列中移出了一个元素。如果双端队列为空,移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。
BlockingDeque 的方法
BlockingDeque 具有 4 组不同的方法用于插入、移除以及对双端队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
抛异常 | 特定值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | addFirst(o) | offerFirst(o) | putFirst(o) | offerFirst(o, timeout, timeunit) |
移除 | removeFirst(o) | pollFirst(o) | takeFirst(o) | pollFirst(timeout, timeunit) |
检查 | getFirst(o) | peekFirst(o) |
抛异常 | 特定值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | addLast(o) | offerLast(o) | putLast(o) | offerLast(o, timeout, timeunit) |
移除 | removeLast(o) | pollLast(o) | takeLast(o) | pollLast(timeout, timeunit) |
检查 | getLast(o) | peekLast(o) |
四组不同的行为方式解释:
- 抛异常:如果试图的操作无法立即执行,抛一个异常。
- 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
- 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
- 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
BlockingDeque 继承自 BlockingQueue
BlockingDeque 接口继承自 BlockingQueue 接口。这就意味着你可以像使用一个 BlockingQueue 那样使用 BlockingDeque。如果你这么干的话,各种插入方法将会把新元素添加到双端队列的尾端,而移除方法将会把双端队列的首端的元素移除。正如 BlockingQueue 接口的插入和移除方法一样。
以下是 BlockingDeque 对 BlockingQueue 接口的方法的具体内部实现:
BlockingQueue | BlockingDeque |
---|---|
add() | addLast() |
offer() x 2 | offerLast() x 2 |
put() | putLast() |
remove() | removeFirst() |
poll() x 2 | pollFirst() |
take() | takeFirst() |
element() | getFirst() |
peek() | peekFirst() |
二、LinkedBlockingDeque源码分析
2.1、LinkedBlockingDeque的lock
LinkedBlockingDeque的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)。LinkedBlockingDeque是一个带有长度的阻塞队列,初始化的时候可以指定队列长度(如果不指定就是Integer.MAX_VALUE),且指定长度之后不允许进行修改。
/** 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();
2.2、数据结构
双向链表
/** 双向链表节点 */
static final class Node<E> {
/**
* 元素值
*/
E item; /**
* 节点前驱
* 1.指向前驱;2.指向this,说明前驱是尾节点,看unlinklast;3.指向null说明没有前驱
*/
Node<E> prev; /**
* 节点后继
* 1.指向后继;2.指向this,说明后继是头结点,看unlinkfirst;3.指向null说明没有后继
*/
Node<E> next; Node(E x) {
item = x;
}
}
2.3、成员变量
//first是双向链表的表头
transient Node<E> first; //last是双向链表的表尾
transient Node<E> last; //count是LinkedBlockingDeque的实际大小,即双向链表中当前节点个数
private transient int count; //是LinkedBlockingDeque的容量,它是在创建LinkedBlockingDeque时指定的
private final int capacity;
2.4、构造函数
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();
if (!linkLast(new Node<E>(e)))
throw new IllegalStateException("Deque full");
}
} finally {
lock.unlock();
}
}
2.5、入队
addFirst,addLast分别调用offerFirst,offerLast,而offerFirst,offerLast和putFirst,putLast都是调用了linkFirst和linkLast。
public void addFirst(E e) {
if (!offerFirst(e))
throw new IllegalStateException("Deque full");
} public void addLast(E e) {
if (!offerLast(e))
throw new IllegalStateException("Deque full");
} 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 {
return linkFirst(node);
} finally {
lock.unlock();
}
} 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();
}
} 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 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 {
while (!linkLast(node))
notFull.await();
} finally {
lock.unlock();
}
}
linkFirst和linkLast
/**
* 设置node为链表头节点,链表满时为false
*/
private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity) //超过容量false
return false;
Node<E> f = first;
node.next = f; //新节点的next指向原first
first = node; //设置node为新的first
if (last == null) //没有尾节点,就将node设置成尾节点
last = node;
else
f.prev = node; //有尾节点,那就将之前first的pre指向新增node
++count; //累加节点数量
notEmpty.signal(); //有新节点入队,通知非空条件队列
return true;
} /**
* 设置node为链表尾节点,链表满时为false
*/
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
Node<E> l = last;
node.prev = l;
last = node;
if (first == null) //为null,说明之前队列空吧,那就first也指向node
first = node;
else
l.next = node; //非null,说明之前的last有值,就将之前的last的next指向node
++count;
notEmpty.signal();
return true;
}
2.6、出队
public E removeFirst() {
E x = pollFirst();
if (x == null) throw new NoSuchElementException();
return x;
} public E removeLast() {
E x = pollLast();
if (x == null) throw new NoSuchElementException();
return x;
} public E pollFirst() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkFirst();
} finally {
lock.unlock();
}
} public E pollLast() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkLast();
} finally {
lock.unlock();
}
} 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();
}
} 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();
}
}
核心方法
/**
* 移除头结点,链表空返回null
*/
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
Node<E> f = first;
if (f == null)
return null; //空返回null
Node<E> n = f.next;
E item = f.item;
f.item = null;
f.next = f; // help GC
first = n;
if (n == null) //说明之前应该只有一个节点,移除头结点后,链表空,现在first和last都指向null了
last = null;
else
n.prev = null; //否则的话,n的pre原来指向之前的first,现在n变为first了,pre指向null
--count;
notFull.signal(); //通知非满条件队列
return item;
} /**
* 移除尾结点,链表空返回null
*/
private E unlinkLast() {
// assert lock.isHeldByCurrentThread();
Node<E> l = last;
if (l == null)
return null;
Node<E> p = l.prev;
E item = l.item;
l.item = null;
l.prev = l; // help GC
last = p;
if (p == null)
first = null;
else
p.next = null;
--count;
notFull.signal();
return item;
} /**
* 移除指定节点:p--》x--》n
*/
void unlink(Node<E> x) {
// assert lock.isHeldByCurrentThread();
Node<E> p = x.prev;
Node<E> n = x.next;
if (p == null) { //prev为null说明x节点为头结点
unlinkFirst();
} else if (n == null) {
unlinkLast(); //nex为null说明待清除节点为尾节点
} else { //否则的话节点处于链表中间
p.next = n; //将p和n互链
n.prev = p;
x.item = null;
// 没有断开x节点链接,可能有其他线程在迭代链表
--count;
notFull.signal();
}
}
2.7、peek方法
public E peek() {
return peekFirst();
} public E peekFirst() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (first == null) ? null : first.item;
} finally {
lock.unlock();
}
}
//peekLast就不贴了
2.8、size方法
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
三、JDK或开源框架中使用
四、使用示例
java.util.ArrayDeque 类提供了可调整大小的阵列,并实现了Deque接口。以下是关于阵列双端队列的要点:Java.util.ArrayDeque
数组双端队列没有容量限制,使他们增长为必要支持使用。
它们不是线程安全的;如果没有外部同步。
不支持多线程并发访问。
null元素被禁止使用在数组deques。
它们要比堆栈Stack和LinkedList快。
此类及其迭代器实现Collection和Iteratorinterfaces方法可选。
类的声明
以下是java.util.ArrayDeque类的声明:
public class ArrayDeque<E>
extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
这里<E>代表一个元素,它可以是任何类。例如,如果你正在构建一个整数数组列表,那么初始化可为
ArrayList<Integer> list = new ArrayList<Integer>();
S.N. | 方法 & 描述 |
---|---|
1 | boolean add(E e) 此方法将添加指定的元素,在此deque队列的末尾。 |
2 | void addFirst(E e) 此方法将添加指定的元素,在此deque队列的前面。 |
3 | void addLast(E e) 此方法将插入指定的元素,在此deque队列的末尾。 |
4 | void clear() 此方法移除此deque队列的元素。 |
5 | ArrayDeque<E> clone() 此方法返回此deque队列的副本。 |
6 | boolean contains(Object o) 如果此deque 队列包含指定的元素,此方法返回true。 |
7 | Iterator<E> descendingIterator() 此方法返回一个迭代器在此deque队列以逆向顺序的元素。 |
8 | E element() 此方法检索,但是不移除此deque队列表示的队列的头部。 |
9 | E getFirst() 此方法检索,但是不移除此deque队列的第一个元素。 |
10 | E getLast() 此方法检索,但是不移除此deque队列的最后一个元素。 |
11 | boolean isEmpty() 如果此deque队列不包含元素,此方法返回true。 |
12 | Iterator<E> iterator() 此方法返回一个迭代器在此deque队列的元素。 |
13 | boolean offer(E e) 此方法将指定的元素,在此deque队列的末尾。 |
14 | boolean offerFirst(E e) 此方法将指定的元素,在此deque队列的前面。 |
15 | boolean offerLast(E e) 此方法将指定的元素,在此deque队列的末尾。 |
16 | E peek() 此方法检索,但是不移除此deque队列表示的队列的头部,如果此deque队列为空,则返回null。 |
17 | E peekFirst() 此方法检索,但是不移除此deque 队列的第一个元素,或者如果此deque 队列为空,则返回null。 |
18 | E peekLast() 此方法检索,但是不移除此deque队列的最后一个元素,如果此deque队列为空,则返回null。 |
19 | E poll() 此方法检索并移除此deque队列表示的队列的头部,如果此deque队列为空,则返回null。 |
20 | E pollFirst() 此方法检索并移除此deque队列的第一个元素,或者如果此deque队列为空,则返回null。 |
21 | E pollLast() 此方法检索并移除此deque队列的最后一个元素,如果此deque队列为空,则返回null。 |
22 | E pop() 这种方法的此deque队列所表示的堆栈弹出一个元素。 |
23 | void push(E e) 这种方法将元素推入此deque队列所表示的堆栈。 |
24 | E remove() 此方法检索并移除此deque队列表示的队列的头部。 |
25 | boolean remove(Object o) 此方法从此deque队列中移除指定元素的单个实例。 |
26 | E removeFirst() 此方法检索并移除此deque队列的第一个元素。 |
27 | boolean removeFirstOccurrence(Object o) 此方法移除此deque队列的指定元素的第一个匹配。 |
28 | E removeLast() 此方法检索并移除此deque队列的最后一个元素。 |
29 | boolean removeLastOccurrence(Object o) 此方法移除此deque队列的指定元素的最后一次出现。 |
30 | int size() 此方法返回在此deque队列的元素个数。 |
31 | object[] toArray() 这个方法返回一个包含所有在此deque队列在适当的序列中元素的数组。 |
阻塞队列之六:LinkedBlockingDeque的更多相关文章
- JUC 并发编程--10, 阻塞队列之--LinkedBlockingDeque 工作窃取, 代码演示
直接上代码 class LinkedBlockingDequeDemo { // 循环是否结束的开关 private static volatile boolean flag1 = true; pri ...
- Java中的阻塞队列
1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...
- java并发编程学习: 阻塞队列 使用 及 实现原理
队列(Queue)与栈(Stack)是数据结构中的二种常用结构,队列的特点是先进先出(First In First Out),而Stack是先进后出(First In Last Out),说得通俗点: ...
- java并发:阻塞队列
第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...
- 聊聊并发(七)——Java中的阻塞队列
3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...
- Java并发编程(十二)-- 阻塞队列
在介绍Java的阻塞队列之前,我们简单介绍一下队列. 队列 队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向 ...
- 阻塞队列 BlockingQueue 详解
转自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487078&idx=2&sn=315f39b6d53 ...
- Java并发(十八):阻塞队列BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ...
- java阻塞队列与非阻塞队列
在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...
随机推荐
- Viewpager+Fragmnet懒汉式注意
1.new的时候不会触发setUserVisibleHint(): 2.setUserVisibleHint()在ViewPager中当前缓冲页跳转,可见到不可见或者不可见到可见的时候触发: 3.ge ...
- QT帮助文档 英文
http://doc.qt.io/archives/qtquick-components-symbian-1.1/qml-button.html
- 通过ssh证书远程登录
在渗透中,经常会发现某管理员主机上保存了大量机器的公私钥用于ssh证书登录.这个时候可以通过这个证书进行远程登录. 先回顾下证书登录通常的配置方法 一.生成不带passphrase的公私钥证书实现免密 ...
- Linux:mii-tool命令详解
mii-tool 用于查看.管理介质的网络接口的状态 语法 mii-tool [-VvRrwl] [-A media,... | -F media] [interface ...] 选项 -V 显示版 ...
- (转)MapReduce Design Patterns(chapter 1)(一)
翻译的是这本书: Chapter 1.Design Patterns and MapReduce MapReduce 是一种运行于成百上千台机器上的处理数据的框架,目前被google,Hadoop等多 ...
- ubuntu16 sogou install
1,下载搜狗deb文件(ubuntu16不要参考搜狗旧的安装文档): http://pinyin.sogou.com/linux/ 2,双击sogoupinyin_2.1.0.0086_amd64.d ...
- Android中的sp和wp指针
经常会在android的framework代码中发现sp<xxx>和wp<xxx>这样的指针,平时看的时候都把他当成一个普通的指针封装过掉了,这几天终于忍不住了,想深入了解一下 ...
- puremvc源码阅读
1.mediator作为ui管理器,是设计成可以list多个notification 2.所有ui想要监听notification,都需要register到facade中 3.puremvc只负责消息 ...
- div横排放置对齐问题;block,inline,inline-block区别
1.左右两个div都设置为float:left,如果右边div没有设置宽度,右边div的宽度会根据div里的内容自动调整. 缺点:不易控制 2.只有左侧div设置为float:left,右侧div设置 ...
- POJ1733 Parity game 【扩展域并查集】*
POJ1733 Parity game Description Now and then you play the following game with your friend. Your frie ...