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

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
函数计算FC,每月15万CU 3个月
可观测监控 Prometheus 版,每月50GB免费额度
简介: `ArrayBlockingQueue` 是Java中一个基于数组的并发队列,具有线程安全的性质。以下是其关键信息的摘要:- **继承实现关系**:它扩展了`AbstractQueue`并实现了`BlockingQueue`接口,确保线程安全的入队和出队操作。- **数据结构**:内部由固定大小的数组支撑,有`takeIndex`和`putIndex`跟踪元素的添加和移除位置,`count`记录队列中的元素数量。- **特点**:队列长度在创建时必须指定且不可变,遵循先进先出(FIFO)原则,当队列满时,添加元素会阻塞,空时,移除元素会阻塞。

 


一、继承实现关系图

image.png

二、低层数据存储结构

public class ArrayBlockingQueue extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    ...
    
    final Object[] items;
    int takeIndex;
    int putIndex;
    int count;
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;
    
    ...
}

image.gif

说明

  • items: 排队
  • takeIndex: 指向队列下一条数据
  • putIndex: 指向队列下一个put的位置
  • count: 队列的数据的数量
  • lock: 添加删除操作对象锁
  • notEmpty: 队列非空阻塞和唤醒条件
  • notFull: 队列是否已满阻塞和唤醒条件

三、特点及优缺点

2.1 特点

  • 是数组实现的线程安全的有界的阻塞队列
  • 线程安全:公用ReentrantLock锁对象来保证多线程间对资源竞争是互斥的
  • 有界:数组是有界的
  • 阻塞:队列空时移除阻塞,队列满时添加会阻塞
  • 先进先出原则
  • 从尾部插入,从头部取出

2.2 优缺点

  • 初始时指定数组大小
  • 存储空间是预先分配
  • 过程中内存开销较小
  • 公用锁保证线程安全,出列入队不能同时进行
  • 效率低

四、源码详解

读取部分源码:

  • 添加任务方法
  • 获取和删除任务方法

4.1 添加任务

/**
 * 如果有足够的空间,则直接把任务插入到队列尾声部 并 返回true <br/>
 * 如果空间不足,则抛IllegalStateException异常 <br/>
 */ 
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}
/**
 * 添加任务 <br/>
 * 添加任务过程中,尝试获取锁时,允许其它线程中断并抛出InterruptedException异常 <br/>
 */
public void put(E e) throws InterruptedException {
    // 非空判断
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;
    // 尝试获取锁,允许在尝试获取锁时其它线程调用尝试获取锁的线程的Thread.interrupt方法来中断线程,这时不用获取到锁,直接抛出InterruptedException
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            // 若队列已满,则等待
            notFull.await();
        // 队列有空间 且被唤醒,则添加到队列尾部
        enqueue(e);
    } finally {
        // 释放锁
        lock.unlock();
    }
}
/**
 * 如果有足够的空间,则直接把任务插入到队列尾声部 并 返回true
 * 如果空间不足,则返回false
 */ 
public boolean offer(E e) {
    // 非空校验
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;
    // 获取对象锁
    lock.lock();
    try {
        // 判断队列是否已满
        if (count == items.length)
            return false;
        else {
            // 将任务插入到队列尾部
            enqueue(e);
            // 返回true 表示插入成功
            return true;
        }
    } finally {
        // 释放锁
        lock.unlock();
    }
}
/**
 * 将元件插入到当前放放位置
 */
private void enqueue(E e) {
    final Object[] items = this.items;
    items[putIndex] = e;
    if (++putIndex == items.length) putIndex = 0;
    count++;
    // 唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回
    notEmpty.signal();
}

image.gif

4.2 获取并删除任务

/**
 * 从队列中取数据 <br/>
 * 如果队列为空,则返回null <br/>
 */
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (count == 0) ? null : dequeue();
    } finally {
        // 释放锁
        lock.unlock();
    }
}
/**
 * 从队列中取数据 <br/>
 * 取任务过程中,尝试获取锁时,允许其它线程中断并抛出InterruptedException异常 <br/>
 */
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 尝试获取锁,允许在尝试获取锁时其它线程调用尝试获取锁的线程的Thread.interrupt方法来中断线程,这时不用获取到锁,直接抛出InterruptedException
    lock.lockInterruptibly();
    try {
        while (count == 0)
            // 若队列为空,则等待
            notEmpty.await();
            // 队列有数据 且被唤醒,则从队列头取数据
        return dequeue();
    } finally {
        // 释放锁
        lock.unlock();
    }
}
/**
 * 从队列头取一个数据 <br/>
 */
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E e = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length) takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();
    return e;
}

image.gif

五、作用

1. 线程池的线程是有限的,新的任务缓存到队列中
2. 无空闲核心线程情况下,新任务缓存到队列中,可起到控制并发量的作用
3. 在高并发情况下,保证线程数控制有一定范围内,从而提高系统的性能和稳定性

image.gif

六、示例

// 核心线程数
int corePoolSize = 10;
// 最大线程数
int maximumPoolSize = 20;
// 空闲线程等待任务存活时间
long keepAliveTime = 10L;
// keepAliveTime的时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 阻塞队列
BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(100);
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, blockingQueue);

image.gif

详细的参数说明上一篇文章


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