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

本文涉及的产品
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,182元/月
简介: **摘要:**本文分析了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






相关文章
|
12天前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
3月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
194 3
|
4月前
|
安全 Java 调度
Netty源码—3.Reactor线程模型二
本文主要介绍了NioEventLoop的执行总体框架、Reactor线程执行一次事件轮询、Reactor线程处理产生IO事件的Channel、Reactor线程处理任务队列之添加任务、Reactor线程处理任务队列之执行任务、NioEventLoop总结。
|
4月前
|
安全 Java
Netty源码—2.Reactor线程模型一
本文主要介绍了关于NioEventLoop的问题整理、理解Reactor线程模型主要分三部分、NioEventLoop的创建和NioEventLoop的启动。
|
4月前
|
JavaScript Java 关系型数据库
家政系统源码,java版本
这是一款基于SpringBoot后端框架、MySQL数据库及Uniapp移动端开发的家政预约上门服务系统。
156 6
家政系统源码,java版本
|
4月前
|
供应链 JavaScript 前端开发
Java基于SaaS模式多租户ERP系统源码
ERP,全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统,它通过信息技术手段,将企业的各个业务流程和资源管理进行整合,以提高企业的运营效率和管理水平,它是一种先进的企业管理理念和信息化管理系统。 适用于小微企业的 SaaS模式多租户ERP管理系统, 采用最新的技术栈开发, 让企业简单上云。专注于小微企业的应用需求,如企业基本的进销存、询价,报价, 采购、销售、MRP生产制造、品质管理、仓库库存管理、财务应收付款, OA办公单据、CRM等。
285 23
|
5月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
421 7
|
5月前
|
Java 关系型数据库 MySQL
Java汽车租赁系统源码(含数据库脚本)
Java汽车租赁系统源码(含数据库脚本)
101 4
|
5月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
5月前
|
Java
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
本文深入解析了ConcurrentHashMap的实现原理,涵盖JDK 7与JDK 8的区别、静态代码块、构造方法、put/get/remove核心方法等。JDK 8通过Node数组+链表/红黑树结构优化并发性能,采用CAS和synchronized实现高效锁机制。文章还详细讲解了hash计算、表初始化、扩容协助及计数更新等关键环节,帮助读者全面掌握ConcurrentHashMap的工作机制。
133 6
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap