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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
可观测可视化 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






相关文章
|
11天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
45 7
|
23天前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
28 4
|
4天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
39 13
|
16天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
17天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
47 12
|
12天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
12天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
19天前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####
|
13天前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。
|
4月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。