LinkedBlockingDeque

LinkedBlockingDeque 能解决什么问题?什么时候使用 LinkedBlockingDeque?

1)LinkedBlockingDeque 是基于双向链表实现的,可以选择有界或无界的双端阻塞队列。
2)LinkedBlockingDeque 使用相同的互斥锁来保证线程安全性,读写操作性能低于 LinkedBlockingQueue。

如何使用 LinkedBlockingDeque?

1)并发场景下,需要作为双端队列使用时,如果只是作为 FIFO 队列使用,则 LinkedBlockingQueue 的性能更高。
2)指定队列的容量,以避免生产速率远高于消费速率时资源耗尽的问题。

使用 LinkedBlockingDeque 有什么风险?

1)未指定容量的情况下,生产速率远高于消费速率时,会导致内存耗尽而 OOM。
2)高并发场景下,性能远低于 LinkedBlockingQueue。
3)由于需要维持前后节点的链接,内存消耗也高于 LinkedBlockingQueue。

LinkedBlockingDeque 核心操作的实现原理?

  • 创建实例
    /** 双向链表节点 */
static final class Node<E> {
/**
* 节点元素,如果节点已经被移除,则为 null
*/
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;
}
} /**
* 头结点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first; /**
* 尾节点
* Invariant: (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(); /** 队列为空时,用于阻塞执行 take 操作的线程的非空条件 */
private final Condition notEmpty = lock.newCondition(); /** 队列已满时,用于阻塞执行 put 操作的线程的非满条件 */
private final Condition notFull = lock.newCondition(); /**
* 创建一个容量为 Integer.MAX_VALUE 的双端阻塞队列
*/
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
} /**
* 创建一个容量为 capacity 的双端阻塞队列
*/
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException();
}
this.capacity = capacity;
}
  • 将目标元素 e 添加到队列头部,如果队列已满,则阻塞等待有可用空间后重试
    /**
* 将目标元素 e 添加到队列头部,如果队列已满,则阻塞等待有可用空间后重试
*/
public void putFirst(E e) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
final Node<E> node = new Node<>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 尝试在头部添加元素
while (!linkFirst(node)) {
// 当前线程在非满条件上等待
notFull.await();
}
} finally {
lock.unlock();
}
} private boolean linkFirst(Node<E> node) {
// 队列已满,则直接返回 false
if (count >= capacity) {
return false;
}
// 读取头节点
final Node<E> f = first;
// 将旧头结点链接到目标节点之后
node.next = f;
// 写入新头节点
first = node;
// 1)当前元素为第一个添加到队列中的元素
if (last == null) {
// 写入尾节点
last = node;
} else {
// 将旧头节点的前置节点设置为新头结点
f.prev = node;
}
// 递增计数
++count;
// 唤醒在非空条件上阻塞等待的线程来读取元素
notEmpty.signal();
return true;
}
  • 如果队列已满,则直接返回 false,否则将目标元素 e 添加到队列头部
    /**
* 如果队列已满,则直接返回 false,否则将目标元素 e 添加到队列头部
*/
@Override
public boolean offerFirst(E e) {
if (e == null) {
throw new NullPointerException();
}
final Node<E> node = new Node<>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
return linkFirst(node);
} finally {
lock.unlock();
}
}
  • 在指定的超时时间内尝试将目标元素 e 添加到队列头部,成功则返回 true
    /**
* 在指定的超时时间内尝试将目标元素 e 添加到队列头部,成功则返回 true
*/
@Override
public boolean offerFirst(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
final Node<E> node = new Node<>(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 头结点添加失败
while (!linkFirst(node)) {
// 已经超时则直接返回
if (nanos <= 0L) {
return false;
}
// 当前线程在非满条件上阻塞等待,唤醒后再次尝试添加
nanos = notFull.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
  • 将目标元素 e 添加到队列尾部,如果队列已满,则阻塞等待有可用空间后重试
    /**
* 将目标元素 e 添加到队列尾部,如果队列已满,则阻塞等待有可用空间后重试
*/
@Override
public void putLast(E e) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
final Node<E> node = new Node<>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 尝试将节点链接到队列尾部
while (!linkLast(node)) {
// 队列已满,当前线程在非满条件上阻塞等待,被唤醒后再次尝试
notFull.await();
}
} finally {
lock.unlock();
}
} private boolean linkLast(Node<E> node) {
// 队列已满,则直接返回 false
if (count >= capacity) {
return false;
}
// 读取尾节点
final Node<E> l = last;
// 将目标节点链接到尾节点之后
node.prev = l;
// 写入尾节点为新增节点
last = node;
// 1)当前元素是第一个加入队列的元素
if (first == null) {
// 写入头结点
first = node;
} else {
// 将旧尾节点的后置节点更新为新增节点
l.next = node;
}
// 递增总数
++count;
// 唤醒在非空条件上等待的线程
notEmpty.signal();
return true;
}
  • 如果队列已满,则直接返回 false,否则将目标元素 e 添加到队列尾部
    /**
* 如果队列已满,则直接返回 false,否则将目标元素 e 添加到队列尾部
*/
@Override
public boolean offerLast(E e) {
if (e == null) {
throw new NullPointerException();
}
final Node<E> node = new Node<>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
return linkLast(node);
} finally {
lock.unlock();
}
}
  • 在指定的超时时间内尝试将目标元素 e 添加到队列尾部,成功则返回 true
    /**
* 在指定的超时时间内尝试将目标元素 e 添加到队列尾部,成功则返回 true
*/
@Override
public boolean offerLast(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
final Node<E> node = new Node<>(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 尝试将目标元素 e 添加到队列尾部
while (!linkLast(node)) {
// 已经超时则直接返回 false
if (nanos <= 0L) {
return false;
}
// 当前线程在非满条件上阻塞等待,被唤醒后再次尝试
nanos = notFull.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
  • 移除并返回头部节点,如果队列为空,则阻塞等待有可用元素之后重试
    /**
* 移除并返回头部节点,如果队列为空,则阻塞等待有可用元素之后重试
* created by ZXD at 6 Dec 2018 T 21:00:25
* @return
* @throws InterruptedException
*/
@Override
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();
}
}
  • 移除并返回尾部节点,如果队列为空,则阻塞等待有可用元素之后重试
    /**
* 移除并返回尾部节点,如果队列为空,则阻塞等待有可用元素之后重试
* created by ZXD at 6 Dec 2018 T 21:02:04
* @return
* @throws InterruptedException
*/
@Override
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,否则移除并返回头部元素
    /**
* 如果队列为空,则立即返回 null,否则移除并返回头部元素
* created by ZXD at 6 Dec 2018 T 21:03:40
* @return
*/
@Override
public E pollFirst() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkFirst();
} finally {
lock.unlock();
}
}
  • 如果队列为空,则立即返回 null,否则移除并返回尾部元素
    /**
* 如果队列为空,则立即返回 null,否则移除并返回尾部元素
* created by ZXD at 6 Dec 2018 T 21:04:43
* @return
*/
@Override
public E pollLast() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return unlinkLast();
} finally {
lock.unlock();
}
}
  • 在指定的超时时间内尝试移除并返回头部元素,如果已经超时,则返回 null
    /**
* 在指定的超时时间内尝试移除并返回头部元素,如果已经超时,则返回 null
* created by ZXD at 6 Dec 2018 T 21:05:21
* @param timeout
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public E pollFirst(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
E x;
// 尝试移除并返回头部元素
while ( (x = unlinkFirst()) == null) {
// 已经超时则返回 null
if (nanos <= 0L) {
return null;
}
// 当前线程在非空条件上阻塞等待,被唤醒后进行重试
nanos = notEmpty.awaitNanos(nanos);
}
// 移除成功则直接返回头部元素
return x;
} finally {
lock.unlock();
}
}
  • 在指定的超时时间内尝试移除并返回尾部元素,如果已经超时,则返回 null
    /**
* created by ZXD at 6 Dec 2018 T 21:08:24
* @param timeout
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public E pollLast(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
E x;
// 尝试移除并返回尾部元素
while ( (x = unlinkLast()) == null) {
// 已经超时则返回 null
if (nanos <= 0L) {
return null;
}
// 当前线程在非空条件上阻塞等待,被唤醒后进行重试
nanos = notEmpty.awaitNanos(nanos);
}
// 移除成功则直接返回尾部元素
return x;
} finally {
lock.unlock();
}
}

LinkedBlockingDeque 源码分析的更多相关文章

  1. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  2. lesson2:java阻塞队列的demo及源码分析

    本文向大家展示了java阻塞队列的使用场景.源码分析及特定场景下的使用方式.java的阻塞队列是jdk1.5之后在并发包中提供的一组队列,主要的使用场景是在需要使用生产者消费者模式时,用户不必再通过多 ...

  3. Spark Scheduler模块源码分析之DAGScheduler

    本文主要结合Spark-1.6.0的源码,对Spark中任务调度模块的执行过程进行分析.Spark Application在遇到Action操作时才会真正的提交任务并进行计算.这时Spark会根据Ac ...

  4. 【java多线程】队列系统之LinkedBlockingDeque源码

    1.简介 上一篇我们介绍了 LinkedBlockingDeque 的兄弟篇 LinkedBlockingQueue .听名字也知道一个实现了 Queue 接口,一个实现了 Deque 接口,由于 D ...

  5. 细说并发5:Java 阻塞队列源码分析(下)

    上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  6. Spark源码分析之二:Job的调度模型与运行反馈

    在<Spark源码分析之Job提交运行总流程概述>一文中,我们提到了,Job提交与运行的第一阶段Stage划分与提交,可以分为三个阶段: 1.Job的调度模型与运行反馈: 2.Stage划 ...

  7. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

  8. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  9. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

随机推荐

  1. Debian/Ubuntu下安装Apache的Mod_Rewrite模块的步骤分享

    启用 Mod_rewrite 模块:sudo a2enmod rewrite 另外,也可以通过将 /etc/apache2/mods-available/rewrite.load 连接到 /etc/a ...

  2. [LeetCode] 203. 移除链表元素

    题目链接:https://leetcode-cn.com/problems/remove-linked-list-elements/ 题目描述: 删除链表中等于给定值 val 的所有节点. 示例: 输 ...

  3. python day2-爬虫实现github登录

    GitHub登录 分析登录页面 开发者工具分析请求 从session请求分析得知: 1.请求的URL为:https://github.com/session 2.该请求为post请求,即需要上传dat ...

  4. ECCV2014 Accepted paper

    今天早上看到小伙伴们说ECCV2014结果出来了, 自己于是赶紧看了下, 感觉ECCV2014显著性的文章和以往的不太一样. 1.Salient Montages from Unconstrained ...

  5. MySQL第一讲 一一一一 数据库入门

    一. MySQL简介与安装 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方 ...

  6. 出现( linker command failed with exit code 1)错误总结(http://blog.csdn.net/hengshujiyi/article/details/21182813)

    这种问题,通常出现在添加第三方库文件或者多人开发时. 这种问题一般是找不到文件而导致的链接错误. 我们可以从如下几个方面着手排查. 1.以如下错误为例,如果是多人开发,你同步完成后发现出现如下的错误. ...

  7. php 多维数组转一维数组

    1.巧用 RecursiveIteratorIterator $k_arrays_arrays_tmp = iterator_to_array(new RecursiveIteratorIterato ...

  8. Linux之目录与路径

    特殊的目录: “.”,代表此层目录 “..”,代表上一层目录 “-”,代表前一个工作目录 “~”,代表“目前用户身份”所在的主文件夹 “~account”,代表account这个用户的主文件夹(acc ...

  9. airflow 简介

    转载:https://zhuanlan.zhihu.com/p/36043468 简介 Apache-Airflow 是Airbnb开源的一款数据流程工具,目前是Apache孵化项目.以非常灵活的方式 ...

  10. DevExpress v18.2版本亮点——Office File API 篇

    行业领先的.NET界面控件——DevExpress v18.2版本亮点详解,本文将介绍了DevExpress Office File API v18.2 的版本亮点,新版30天免费试用!点击下载> ...