一、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)    

四组不同的行为方式解释:

  1. 抛异常:如果试图的操作无法立即执行,抛一个异常。
  2. 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
  3. 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
  4. 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 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的更多相关文章

  1. JUC 并发编程--10, 阻塞队列之--LinkedBlockingDeque 工作窃取, 代码演示

    直接上代码 class LinkedBlockingDequeDemo { // 循环是否结束的开关 private static volatile boolean flag1 = true; pri ...

  2. Java中的阻塞队列

    1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...

  3. java并发编程学习: 阻塞队列 使用 及 实现原理

    队列(Queue)与栈(Stack)是数据结构中的二种常用结构,队列的特点是先进先出(First In First Out),而Stack是先进后出(First In Last Out),说得通俗点: ...

  4. java并发:阻塞队列

    第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...

  5. 聊聊并发(七)——Java中的阻塞队列

    3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...

  6. Java并发编程(十二)-- 阻塞队列

    在介绍Java的阻塞队列之前,我们简单介绍一下队列. 队列 队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向 ...

  7. 阻塞队列 BlockingQueue 详解

    转自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487078&idx=2&sn=315f39b6d53 ...

  8. Java并发(十八):阻塞队列BlockingQueue

    阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ...

  9. java阻塞队列与非阻塞队列

    在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...

随机推荐

  1. 一些常用的UI控件

    让你的 EditText 全部清除: 网址:https://github.com/MrFuFuFu/ClearEditText

  2. MATLAB基础知识tips

    网络论坛资源: MATLAB中文论坛https://i.cnblogs.com/EditPosts.aspx?opt=1 circshift 移位函数,circshift(A,SHIFTSIZE).S ...

  3. leetCode之Median of Two Sorted Arrays

    [题目描述] There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of ...

  4. 求小于等于k长度的最大区间和

    题意 给出一个序列,求长度小于等于k的最大区间和并输出起点和终点 1<=n<=100000 1<=k<=n   题解:先算出前缀和,利用单调队列的性质,在单调队列中存储sum[ ...

  5. (转)MapReduce Design Patterns(chapter 4 (part 1))(七)

    Chapter 4. Data Organization Patterns 与前面章节的过滤器相比,本章是关于数据重组.个别记录的价值通常靠分区,分片,排序成倍增加.特别是在分布式系统中,因为这能提高 ...

  6. flowable EngineConfiguration的实现分析(2)

    EngineConfiguration的实现类是一个抽象类:AbstractEngineConfiguration 一.引擎配置的分类 继承 AbsractEngineConfiguration的子类 ...

  7. oracle对三个列求sum

    oracle数据库对test_table表的三个列count1,count2,count3求sum的两种sql,做个记录 第一种 select sum (case when count1 is not ...

  8. webpack 分析

    编译后 自动打开浏览器 可视 //npm install --save-dev webpack-bundle-analyzer //webpack.base.conf.js const BundleA ...

  9. Android编程 高德地图 中如何重写 定位按键 的触发事件 (com.amap.api.maps2d.LocationSource)点击定位后不仅定位在地图中心点上而且可以设置地图的缩放大小和提示

    在利用高德地图来编写自己的APP的时候,发现了一种对定位按键的重写方法,那就是利用   com.amap.api.maps2d.LocationSource  接口来重写. 什么是定位按键呢,下图中右 ...

  10. python的文件编码注释

    在python源文件的第一行或第二行写入如下内容: # -*- coding:gbk -*- # 设置源文件编码格式为gbk 或 # -*- coding:utf-8 -*- # 设置源文件编码格式为 ...