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. c语言中字符串跨行书写的问题

    字符串常量定义时的换行问题     如果我们在一行代码的行尾放置一个反斜杠,c语言编译器会忽略行尾的换行符,而把下一行的内容也算作是本行的内容.这里反斜杠起到了续行的作用.        如果我们不使 ...

  2. [LeetCode] 132. 分割回文串 II

    题目链接 : https://leetcode-cn.com/problems/palindrome-partitioning-ii/ 题目描述: 给定一个字符串 s,将 s 分割成一些子串,使每个子 ...

  3. HBase Shell 的常用操作总结

      1,创建表:create 't1','f1','f2','f3'                   #-------t1是表名,f1,f2,f3是列族名   2,查看所有的表:list   3, ...

  4. 自动清理ES索引脚本

    #/bin/bash #指定日期(3个月前) DATA=`date -d "3 month ago" +%Y.%m.%d` #当前日期 time=`date` #删除3个月前的日志 ...

  5. 【问题解决方案】Centos操作文件vim-No write since last change (add ! to override)

    参考链接 CSDN:Centos 7 操作文件No write since last change (add ! to override) 问题描述: :q或者:wq退出失败,显示如No write ...

  6. react native 在vscode上运行

    1.在用react-native init xxx 创建rn项目之后,在Android目录中创建local.properties文件 =后面接上sdk地址 2.react-native start 命 ...

  7. python 查询Neo4j多节点的多层关系

    需求:查询出满足3人及3案有关系的集合 # -*- coding: utf-8 -*- from py2neo import Graph import psycopg2 # 二维数组查找 def fi ...

  8. iOS 审核app被拒绝的各种理由以及翻译

    原 apps被拒绝的各种理由以及翻译:http://my.oschina.net/201003674/blog/356189#OSC_h1_3 1. Terms and conditions(法律与条 ...

  9. /proc/sys/fs/file-max

    Linux的/proc/sys/fs/file-max决定了当前内核可以打开的最大的文件句柄数. 查看当前的值: cat /proc/sys/fs/file-max 这个值在kernel的文档里是这样 ...

  10. 北京师范大学第十五届ACM决赛-重现赛E Euclidean Geometry (几何)

    链接:https://ac.nowcoder.com/acm/contest/3/E 来源:牛客网 Euclidean Geometry 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ ...