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

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
可观测监控 Prometheus 版,每月50GB免费额度
函数计算FC,每月15万CU 3个月
简介: `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

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


相关文章
|
2月前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
2月前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
193 64
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
57 12
|
29天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
2月前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
120 38
|
2月前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####
|
2月前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
351 2
|
3月前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
4月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口