Java线程池ThreadPoolExcutor源码解读详解08-阻塞队列之LinkedBlockingDeque

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: **摘要:**本文分析了Java中的LinkedBlockingDeque,它是一个基于链表实现的双端阻塞队列,具有并发安全性。LinkedBlockingDeque可以作为有界队列使用,容量由构造函数指定,默认为Integer.MAX_VALUE。队列操作包括在头部和尾部的插入与删除,这些操作由锁和Condition来保证线程安全。例如,`linkFirst()`和`linkLast()`用于在队首和队尾插入元素,而`unlinkFirst()`和`unlinkLast()`则用于删除首尾元素。队列的插入和删除方法根据队列是否满或空,可能会阻塞或唤醒等待的线程,这些操作通过`notFul

💪🏻 制定明确可量化的目标,坚持默默的做事。




一、继承实现关系图

image.gif 1711819665809.png

二、低层数据存储结构

2.1 主要属性

public class LinkedBlockingDeque<E>
    extends AbstractQueue<E>
    implements BlockingDeque<E>, java.io.Serializable {
...
    static final class Node<E> {
        E item;
        Node<E> prev;
        Node<E> next;
        Node(E x) {
            item = x;
        }
    }
    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();
...
}

image.gif

说明:

  • Node: 基于链表实现的双向链表结构
  • first: 始终指向链表头,本身不存储元素
  • last: 指向链表尾
  • count: 链表元素的数量
  • capacity: 链表的容量
  • lock: 存取对象锁(存取同一把锁)
  • notEmpty: 队列非空阻塞和唤醒条件
  • notFull: 队列是否已满阻塞和唤醒条件

2.2 构造器:

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) {
        ...
    }

image.gif

说明:

  • 默认无参构造器,容量为Intege.MAX_VALUE
  • 指定队列容量构造器
  • 初始化元素构造器,容量为Integer.MAX_VALUE

三、特点及优缺点

  1. 双向并发无界阻塞链表
  2. 线程安全
  3. 可从队头或队尾插入元素、可从队头或队尾删除元素
  4. 插入和删除元素是同一把锁,效率不高
  5. 通过ReentrantLock实现锁
  6. 利用Condition实现队列的阻塞等待唤醒

四、源码详解

核心入队方法:

  • offerFirst(E e):添加头节点元素(不阻塞)
  • offerLast(E e):添加尾节点元素(不阻塞)
  • putFirst(E e):添加头节点元素,若添加不成功则阻塞直到被唤醒且添加成功
  • putLast(E e):添加尾节点元素,若添加不成功则阻塞直到被唤醒且添加成功
  • offerFirst(E e, long timeout, TimeUnit unit):添加头节点元素,若添加不成功则阻塞,直到被唤醒且添加成功或超时退出
  • offerLast(E e, long timeout, TimeUnit unit):添加尾节点元素,若添加不成功则阻塞,直到被唤醒且添加成功或超时退出
  • linkFirst(Node<E> node):头部插入(private)
  • linkLast(Node<E> node):尾部插入(private)

       PS:添加头节点最终调linkFirst,添加尾节点最终调linkLast,下面详细阅读这两个方法

4.1 linkFirst(Node<E> node)

private boolean linkFirst(LinkedBlockingDeque.Node<E> node) {
    // 当前容量大于队列最大容量时,返回插入失败
    if (count >= capacity)
        return false;
    // 队列头结点
    LinkedBlockingDeque.Node<E> f = first;
    // 原来的头结点作为 新插入结点的后一个结点
    node.next = f;
    // 替换头结点 为新插入的结点
    first = node;
    // 尾结点不存在,将尾结点置为当前新插入的结点
    if (last == null)
        last = node;
    else
        // 原来的头结点的上一个结点为当前新插入的结点
        f.prev = node;
    // 当前容量加1
    ++count;
    // 唤醒读取时因队列中无元素而导致阻塞的线程
    notEmpty.signal();
    // 返回插入成功信息
    return true;
}

image.gif

4.2 linkLas(Node<E> node)

private boolean linkLast(Node<E> node) {
    // 当前容量大于队列最大容量时,直接返回插入失败
    if (count >= capacity)
        return false;
    // 获取尾节点
    Node<E> l = last;
    // 将新插入的前一个节点指向原来的尾节点
    node.prev = l;
    // 尾结点设置为新插入的结点
    last = node;
    // 若头结点为空,新插入的结点作为头节点
    if (first == null)
        first = node;
    else
        // 将原尾结点的下一个结点指向新插入的节点
        l.next = node;
    // 当前容量加1
    ++count;
    // 唤醒读取时因队列中无元素而导致阻塞的线程
    notEmpty.signal();
    // 返回插入成功信息
    return true;
}

image.gif

核心出队方法:

  • pollFirst():删除头节点(不阻塞)
  • pollLast():删除尾节点(不阻塞)
  • takeFirst():删除头节点,删除不成功则阻塞直到被唤醒且删除成功
  • takeLast():删除尾节点,删除不成功则阻塞直到被唤醒且删除成功
  • pollFirst(long timeout, TimeUnit unit):删除头节点,若删除不成功则阻塞直到被唤醒且删除成功或者超时退出
  • pollLast(long timeout, TimeUnit unit):删除尾节点,若删除不成功则阻塞直到被唤醒且删除成功或者超时退出
  • unlinkFirst():删除头节点(private)
  • unlinkLast():删除尾节点(private)

       PS:添加头节点最终调linkFirst,添加尾节点最终调linkLast,下面详细阅读这两个方法

4.3 unlinkFirst()

// 删除头结点
private E unlinkFirst() {
    // 获取当前头结点
    Node<E> f = first;
    // 若头结点为空,直接返回空
    if (f == null)
        return null;
    // 获取头结点的下一个结点
    Node<E> n = f.next;
    // 获取头结点元素(记录return需要用到的删除了哪个元素)
    E item = f.item;
    // 将头结点元素置为null
    f.item = null;
    // 将头结点的下一个结点指向自己 方便gc
    f.next = f;
    // 设置头结点为原头结点的下一个结点
    first = n;
    // 若原头结点的下一个结点不存在(队列中没有了结点)
    if (n == null)
        // 将尾结点也置为null
        last = null;
    else
        // 新的头结点的前一个结点指向null,因为他已经作为了头结点 所以不需要指向上一个结点
        n.prev = null;
    // 当前数量减1
    --count;
    // 唤醒因添加元素时队列容量满导致阻塞的线程
    notFull.signal();
    // 返回原来的头结点中的元素
    return item;
}

image.gif

4.4 unlinkLast()

// 删除尾结点
private E unlinkLast() {
    // 获取当前尾结点
    Node<E> l = last;
    // 若尾结点不存在,直接返回null
    if (l == null)
        return null;
    // 获取当前尾结点的上一个结点
    Node<E> p = l.prev;
    // 获取当前尾结点中的元素,需要返回记录
    E item = l.item;
    // 将当前尾结点的元素置为null
    l.item = null;
    // 并将当前尾结点的上一个结点指向自己,方便gc
    l.prev = l;
    // 设置新的尾结点,为原来尾结点的前一个结点
    last = p;
    // 若无新的尾结点,头结点置为空(队列中没有了结点)
    if (p == null)
        first = null;
    else
        // 将新的尾结点的下一个结点指向null,因为他已经为尾结点所以不需要指向下一个结点
        p.next = null;
    // 数量减1
    --count;
    // 唤醒因添加元素时队列容量满导致阻塞的线程
    notFull.signal();
    // 返回原来的尾结点元素
    return item;
}

image.gif

五、作用及使用场景

  • 线程池阻塞队列
  • 生产者消费者模式
  • 任务调度FIFO和FILO






相关文章
|
2天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
12 5
|
2天前
|
监控 安全 NoSQL
采用java+springboot+vue.js+uniapp开发的一整套云MES系统源码 MES制造管理系统源码
MES系统是一套具备实时管理能力,建立一个全面的、集成的、稳定的制造物流质量控制体系;对生产线、工艺、人员、品质、效率等多方位的监控、分析、改进,满足精细化、透明化、自动化、实时化、数据化、一体化管理,实现企业柔性化制造管理。
21 3
|
2天前
|
存储 Java
【Java】实现一个简单的线程池
,如果被消耗完了就说明在规定时间内获取不到任务,直接return结束线程。
11 0
|
3天前
|
存储 Java
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
|
3天前
|
数据采集 监控 安全
java数字工厂MES系统全套源码Java+idea+springboot专业为企业提供智能制造MES解决方案
"MES" 指的是制造执行系统(Manufacturing Execution System)。MES在制造业中扮演着至关重要的角色,它是位于企业资源计划(ERP)系统和车间控制系统之间的系统,用于实时收集、管理、分析和报告与制造过程相关的数据。
10 0
|
3天前
|
移动开发 监控 供应链
JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(app、小程序、H5、台后管理端)
一开始接触MES系统,很多人会和博主一样,对MES细节的应用不了解,这样很正常,因为MES系统相对于其他系统来讲应用比较多!
15 1
JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(app、小程序、H5、台后管理端)
|
4天前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
4天前
|
存储 运维 Java
java云his系统源码一站式诊所SaaS系统Java版云HIS系统 八大特点
HIS系统采用面向技术架构的分析与设计方法,应用多层次应用体系架构设计,运用基于构件技术的系统搭建模式与基于组件模式的系统内核结构。通过建立统一接口标准,实现数据交换和集成共享,通过统一身份认证和授权控制,实现业务集成、界面集成。
29 1
|
5天前
|
Java 关系型数据库 MySQL
java+B/S架构医院绩效考核管理系统源码 医院绩效管理系统4大特点
医院绩效考核管理系统,采用多维度综合绩效考核的形式,针对院内实际情况分别对工作量、KPI指标、科研、教学、管理等进行全面考核。医院可结合实际需求,对考核方案中各维度进行灵活配置,对各维度的权重、衡量标准、数据统计方式进行自定义维护。
13 0
|
5天前
|
Java 数据挖掘 BI
Java医院绩效考核系统源码B/S+avue+MySQL助力医院实现精细化管理
医院绩效考核系统目标是实现对科室、病区财务指标、客户指标、流程指标、成长指标的全面考核、分析,并与奖金分配、学科建设水平评价挂钩。
32 0