禅与计算机 禅与计算机
首页
  • Java基础

    • 一文搞懂Java核心技术
    • Java面向对象知识点大总结,建议收藏
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
    • Java异常:从原理到实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • AI时代专注力培养
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • Elasticsearch核心原理与架构设计
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 一文搞懂Java核心技术
    • Java面向对象知识点大总结,建议收藏
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
    • Java异常:从原理到实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • AI时代专注力培养
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • Elasticsearch核心原理与架构设计
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • 浅谈传统并发编程的优化思路
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 浅谈池化技术的优雅关闭
    • 浅谈守护线程与进程优雅关闭
    • 浅谈并发编程等待通知模型
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 深入理解synchronized同步机制
    • 详解JUC包下的锁
    • 详解JUC包下各种锁的使用
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • 详解Java并发流程控制工具
    • Java并发容器总结
    • 深入解析CopyOnWriteArrayList
    • 详解Java并发编程volatile关键字
    • 聊聊JVM中安全点的概念
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步IO密集型任务最佳实践
    • CompletableFuture异步多任务最佳实践
    • CompletableFuture组合流水线任务实践
    • 实现一个简单实用的的并发同步模型
    • 解决Java并发问题的常见思路
    • 来聊一个有趣的限流器RateLimiter
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • ArrayBlockingQueue源码分析
      • 阻塞队列简介
        • 阻塞队列的历史
        • 阻塞队列的思想
      • ArrayBlockingQueue常见方法及测试
      • ArrayBlockingQueue源码分析
        • 从类图开始ArrayBlockingQueue的设计
        • ArrayBlockingQueue构造方法
        • put(E e)和take()方法
        • offer(E e)和poll()方法
        • offer(E e, long timeout, TimeUnit unit)和poll(long timeout, TimeUnit unit)
        • add(E e)和remove()
      • ArrayBlockingQueue相关面试题
      • 参考文献
    • PriorityQueue源码分
    • DelayQueue源码解析
  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • 并发编程
sharkchili
2026-04-10
目录

ArrayBlockingQueue源码分析

# 阻塞队列简介

# 阻塞队列的历史

Java阻塞队列的历史可以追溯到JDK1.5版本,Java平台增加了java.util.concurrent(即我们常说的JUC包),这其中包含了各种并发流程控制工具、并发容器等。这其中自然也包含了我们这篇文章所讨论的阻塞队列。

为了实现在高并发场景下,多线程之间数据共享的问题,在JDK1.5版本,出现了我们所熟知的ArrayBlockingQueue和LinkedBlockingQueue,一种带有生产者-消费者模式所实现的并发容器。其中,ArrayBlockingQueue是有界队列,即添加的元素达到上限之后,再次添加就会被阻塞或者抛出异常。而LinkedBlockingQueue则由链表构成的队列,正是因为链表的特性,所以LinkedBlockingQueue在添加元素上并不会向ArrayBlockingQueue那样有着较多的约束,所以LinkedBlockingQueue设置队列是否有界是可选的(注意这里的无界并不是指可以添加任务数量的元素,而是说队列的大小默认为Integer.MAX_VALUE,近乎于无限大)。

随着Java的不断发展,JDK后续的几个版本又对阻塞队列进行了不少的更新和完善:

  1. JDK1.6版本:增加SynchronousQueue,一个不存储元素的阻塞队列。
  2. JDK1.7版本:增加TransferQueue,一个支持更多操作的阻塞队列。
  3. JDK1.8版本:增加DelayQueue,一个支持延迟获取元素的阻塞队列。

# 阻塞队列的思想

阻塞队列就是典型的生产者-消费者模型,它可以做到以下几点:

  1. 当阻塞队列数据为空时,所有的消费者线程都会被阻塞,等待队列非空。
  2. 当生产者往队列里填充数据后,队列就会通知消费者队列非空,消费者此时就可以进来消费。
  3. 当阻塞队列因为消费者消费过慢或者生产者存放元素过快导致队列填满时无法容纳新元素时,生产者就会被阻塞,等待队列非满时继续存放元素。
  4. 当消费者从队列中消费一个元素之后,队列就会通知生产者队列非满,生产者可以继续填充数据了。

总结一下:阻塞队列就说基于非空和非满两个条件实现生产者和消费者之间的交互,尽管这些交互流程和等待通知的机制实现非常复杂,好在Doug Lea的操刀之下已将阻塞队列的细节屏蔽,我们只需调用put、take、offfer、poll等API即可实现多线程之间的生产和消费。

这也使得阻塞队列在多线程开发中有着广泛的运用,最常见的例子无非是我们的线程池,从源码中我们就能看出当核心线程无法及时处理任务时,这些任务都会扔到workQueue中。

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
       
    }
1
2
3
4
5
6
7
8
9

# ArrayBlockingQueue常见方法及测试

简单了解了阻塞队列的历史之后,我们就开始重点讨论本篇文章所要介绍的并发容器——ArrayBlockingQueue。为了后续更加深入的了解ArrayBlockingQueue,我们不妨基于下面几个实例了解以下ArrayBlockingQueue的使用。

先看看第一个例子,我们这里会用两个线程分别模拟生产者和消费者,生产者生产完会使用put方法生产10个元素给消费者进行消费,当队列元素达到我们设置的上限5时,put方法就会阻塞。 同理消费者也会通过take方法消费元素,当队列为空时,take方法就会阻塞消费者线程。这里笔者为了保证消费者能够在消费完10个元素后及时退出。便通过倒计时门闩,来控制消费者结束,生产者在这里只会生产10个元素。当消费者将10个元素消费完成之后,按下倒计时门闩,所有线程都会停止。

public class ProducerConsumerExample {

    public static void main(String[] args) throws InterruptedException {



        // 创建一个大小为 5 的 ArrayBlockingQueue
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);

        // 创建生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    // 向队列中添加元素,如果队列已满则阻塞等待
                    queue.put(i);
                    System.out.println("生产者添加元素:" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        CountDownLatch countDownLatch = new CountDownLatch(1);
        
        // 创建消费者线程
        Thread consumer = new Thread(() -> {
            try {
                int count = 0;
                while (true) {

                    // 从队列中取出元素,如果队列为空则阻塞等待
                    int element = queue.take();
                    System.out.println("消费者取出元素:" + element);
                    ++count;
                    if (count == 10) {
                        break;
                    }
                }

                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        // 启动线程
        producer.start();
        consumer.start();

        // 等待线程结束
        producer.join();
        consumer.join();

        countDownLatch.await();

        producer.interrupt();
        consumer.interrupt();


    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

代码输出结果如下,可以看到只有生产者往队列中投放元素之后消费者才能消费,这也就意味着当队列中没有数据的时消费者就会阻塞,等待队列非空再继续消费。

生产者添加元素:1
生产者添加元素:2
消费者取出元素:1
消费者取出元素:2
消费者取出元素:3
生产者添加元素:3
生产者添加元素:4
生产者添加元素:5
消费者取出元素:4
生产者添加元素:6
消费者取出元素:5
生产者添加元素:7
生产者添加元素:8
生产者添加元素:9
生产者添加元素:10
消费者取出元素:6
消费者取出元素:7
消费者取出元素:8
消费者取出元素:9
消费者取出元素:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

了解了put、take这两个会阻塞的存和取方法之后,我我们再来看看阻塞队列中非阻塞的入队和出队方法offer和poll。

如下所示,我们设置了一个大小为3的阻塞队列,我们会尝试在队列用offer方法存放4个元素,然后再从队列中用poll尝试取4次。

public class OfferPollExample {

    public static void main(String[] args) {
        // 创建一个大小为 3 的 ArrayBlockingQueue
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        // 向队列中添加元素
        System.out.println(queue.offer("A"));
        System.out.println(queue.offer("B"));
        System.out.println(queue.offer("C"));

        // 尝试向队列中添加元素,但队列已满,返回 false
        System.out.println(queue.offer("D"));

        // 从队列中取出元素
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());

        // 尝试从队列中取出元素,但队列已空,返回 null
        System.out.println(queue.poll());
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

最终代码的输出结果如下,可以看到因为队列的大小为3的缘故,我们前3次存放到队列的结果为true,第4次存放时,由于队列已满,所以存放结果返回false。这也是为什么我们后续的poll方法只得到了3个元素的值。

true
true
true
false
A
B
C
null
1
2
3
4
5
6
7
8

了解了阻塞存取和非阻塞存取,我们再来看看阻塞队列的一个比较特殊的操作,某些场景下,我们希望能够一次性将阻塞队列的结果存到列表中再进行批量操作,我们就可以使用阻塞队列的drainTo方法,这个方法会一次性将队列中所有元素存放到列表,如果队列中有元素,且成功存到list中则drainTo会返回本次转移到list中的元素数,反之若队列为空,drainTo则直接返回0。

public class DrainToExample {

    public static void main(String[] args) {
        // 创建一个大小为 5 的 ArrayBlockingQueue
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);

        // 向队列中添加元素
        queue.add(1);
        queue.add(2);
        queue.add(3);
        queue.add(4);
        queue.add(5);

        // 创建一个 List,用于存储从队列中取出的元素
        List<Integer> list = new ArrayList<>();

        // 从队列中取出所有元素,并添加到 List 中
        queue.drainTo(list);

        // 输出 List 中的元素
        System.out.println(list);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

代码输出结果如下

[1, 2, 3, 4, 5]
1

# ArrayBlockingQueue源码分析

自此我们对阻塞队列的使用有了基本的印象,接下来我们就可以进一步了解一下ArrayBlockingQueue的工作机制了。

# 从类图开始ArrayBlockingQueue的设计

在了解ArrayBlockingQueue的具体细节之前,我们先来看看ArrayBlockingQueue的类图,从图中我们可以看出,ArrayBlockingQueue继承了阻塞队列BlockingQueue这个接口,不难猜出通过继承BlockingQueue这个接口之后,ArrayBlockingQueue就拥有了阻塞队列那些常见的操作行为。

同时ArrayBlockingQueue还继承了AbstractQueue这个抽象类,这个继承了AbstractCollection和Queue的抽象类,从抽象类的特定和语义我们也可以猜出,这个继承关系使得ArrayBlockingQueue拥有了队列的常见操作。

在这里插入图片描述

所以我们是否可以得出这样一个结论,通过继承AbstractQueue获得队列所有的操作模板,其实现的入队和出队操作的整体框架。然后ArrayBlockingQueue通过继承BlockingQueue获取到阻塞队列的常见操作并将这些操作实现,填充到AbstractQueue模板方法的细节中,由此ArrayBlockingQueue成为一个完整的阻塞队列。

在这里插入图片描述

为了印证这一点,我们到源码中一探究竟。首先我们先来看看AbstractQueue,从类的继承关系我们可以大致得出,它通过AbstractCollection获得了集合的常见操作方法,然后通过Queue接口获得了队列的特性。

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {

		//略
}
1
2
3
4
5
6

对于集合的操作无非是增删改查,所以我们不妨从添加方法入手,从源码中我们可以看到,它实现了AbstractCollection的add方法,其内部逻辑如下:

  1. 调用继承Queue接口的来的offer方法,如果offer成功则返回true。
  2. 如果offer失败,即代表当前元素入队失败直接抛异常。
public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
1
2
3
4
5
6

而AbstractQueue中并没有对Queue的offer的实现,很明显这样做的目的是定义好了add的核心逻辑,将offer的细节交由其子类即我们的ArrayBlockingQueue实现。

到此,我们对于抽象类AbstractQueue的分析就结束了,我们继续看看ArrayBlockingQueue中另一个重要的继承接口BlockingQueue。

点开BlockingQueue之后,我们可以看到这个接口同样继承了Queue接口,这就意味着它也具备了队列所拥有的所有行为。同时,它还定义了自己所需要实现的方法。

public interface BlockingQueue<E> extends Queue<E> {
  
   //元素入队成功返回true,反之则会抛出异常IllegalStateException
    boolean add(E e);

 	//元素入队成功返回true,反之返回false
    boolean offer(E e);

 	//元素入队成功则直接返回,如果队列已满元素不可入队则将线程阻塞,因为阻塞期间可能会被打断,所以这里方法签名抛出了InterruptedException
    void put(E e) throws InterruptedException;

   //和上一个方法一样,只不过队列满时只会阻塞单位为unit,时间为timeout的时长,如果在等待时长内没有入队成功则直接返回false。
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

   //从队头取出一个元素,如果队列为空则阻塞等待,因为会阻塞线程的缘故,所以该方法可能会被打断,所以签名定义了InterruptedException
    E take() throws InterruptedException;

 	//取出队头的元素并返回,如果当前队列为空则阻塞等待timeout且单位为unit的时长,如果这个时间段没有元素则直接返回null。
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

   	//获取队列剩余元素个数
    int remainingCapacity();

 	//删除我们指定的对象,如果成功返回true,反之返回false。
    boolean remove(Object o);

    //判断队列中是否包含指定元素
    public boolean contains(Object o);

 	//将队列中的元素全部存到指定的集合中  
    int drainTo(Collection<? super E> c);

    //转移maxElements个元素到集合中
    int drainTo(Collection<? super E> c, int maxElements);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

所以,通过对接口注释的阅读,我们大抵可以将阻塞队列的API归纳成以下几类,由此我们可以看出阻塞队列对于增删查的操作还是很灵活的。

在这里插入图片描述

自此我们了解了BlockingQueue的常见操作后,我们就知道了ArrayBlockingQueue通过继承BlockingQueue的方法并实现后,填充到AbstractQueue的方法上,由此我们便知道了上文中AbstractQueue的add方法的offer方法是哪里是实现的了。

public boolean add(E e) {
		//AbstractQueue的offer来自下层的ArrayBlockingQueue从BlockingQueue继承并实现的offer方法
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
1
2
3
4
5
6
7

# ArrayBlockingQueue构造方法

了解ArrayBlockingQueue的细节前,我们不妨先看看其构造函数,了解一下其初始化过程。从源码中我们可以看出ArrayBlockingQueue有3个构造方法,而最核心的构造方法就是下方这一个。

可以看到这个构造方法会用capacity初始化容量,用fair的值设置锁的公平性,这里面有一个比较核心的成员变量,即notEmpty 和notFull ,它们是实现生产者和消费者有序工作的关键所在,这一点笔者会在后续的源码解析中详细说明,这里我们只需初步了解一下阻塞队列的构造即可。

public ArrayBlockingQueue(int capacity, boolean fair) {
		//如果设置的队列大小小于0,则直接抛出IllegalArgumentException
        if (capacity <= 0)
            throw new IllegalArgumentException();
        //初始化一个数组用于存放队列的元素
        this.items = new Object[capacity];
        //创建阻塞队列流程控制的锁
        lock = new ReentrantLock(fair);
        //用lock锁创建两个条件控制队列生产和消费
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
1
2
3
4
5
6
7
8
9
10
11
12

另外两个构造方法都是基于上述的构造方法,默认情况下,我们会使用下面这个构造方法,该构造方法就意味着ArrayBlockingQueue用的是非公平锁,即各个生产者或者消费者线程收到通知后,对于锁的争抢是随机的。

 public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
1
2
3

还有一个不怎么常用的构造方法,在初始化容量和锁的非公平性之后,它还提供了一个Collection参数,从源码中不难看出这个构造方法是将外部传入的集合的元素在初始化时直接存放到阻塞队列中。

public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        //初始化容量和锁的公平性
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        //上锁并将c中的元素存放到ArrayBlockingQueue底层的数组中
        lock.lock(); 
        try {
            int i = 0;
            try {
            	//遍历并添加元素到数组中
                for (E e : c) {
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            //记录当前队列容量
            count = i;
 			//更新下一次put或者offer或用add方法添加到队列底层数组的位置
            putIndex = (i == capacity) ? 0 : i;
        } finally {
        	//完成遍历后释放锁
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# put(E e)和take()方法

还记得我们上文中关于生产者和消费者的例子吗?它们就是通过put(E e)和take()方法实现多线程之间队列数据有序的生产和消费的。而这两个方法实现的关键就是在于两个条件对象——非空和非满。 也就是我们从上文的构造方法中就会看到notEmpty和notFull对象,接下来笔者就通过两张图让大家了解一下这两个条件是如何在阻塞队列中运用的。

假设我们的代码消费者先启动,当它发现队列中没有数据,那么非空条件就会将这个线程挂起,即等待条件非空时挂起。然后CPU执行权到达生产者,生产者发现队列中可以存放数据,于是将数据存放进去,通知此时条件非空,此时消费者就会被唤醒到队列中使用take等方法获取值了。

在这里插入图片描述

随后的执行中,生产者生产速度远远大于消费者消费速度,于是生产者将队列塞满后再次尝试将数据存入队列,发现队列已满,于是阻塞队列就将当前线程挂起,等待非满。然后消费者拿着CPU执行权进行消费,于是队列可以存放新数据了,发出一个非满的通知,此时挂起的生产者就会等待CPU执行权到来时再次尝试将数据存到队列中。

在这里插入图片描述

了解阻塞队列的基于两个条件的交互流程之后,我们不妨看看put和take的源码。从put的源码中,我们可以印证上述所说,可以看到put方法做了以下几件事:

  1. 上锁,避免获取数据时出现线程安全问题。
  2. 如果count和值等于数组长度,则说明队列已经满了,本地put操作无法将数据存到ArrayBlockingQueue底层的数组中,调用notFull.await()将当前线程打断存放到AQS队列中,等待条件非满时完成插入。
  3. 如果count和值不等于数组长度,则说明当前队列还可以存放元素,则调用enqueue方法将元素存放到数组中。
  4. 完成put操作后释放锁。
public void put(E e) throws InterruptedException {
        checkNotNull(e);
        //获取锁并上锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
        //如果count等数组长度则说明队列已满,将当前线程打断等待条件非空
            while (count == items.length)
                notFull.await();
         //如果队列可以存放元素,则调用enqueue将元素入队
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

了解了put的整体流程之后,我们继续深入查看一下enqueue方法的实现细节,从源码中可以看到入队操作的逻辑就是在数组中追加一个新元素,整体执行步骤为:

  1. 获取ArrayBlockingQueue底层的数组items。
  2. 将元素存到putIndex位置。
  3. 更新putIndex到下一个位置,如果putIndex等于队列长度,则说明putIndex已经到达数组末尾了,下一次插入则需要0开始。(ArrayBlockingQueue用到了循环队列的思想,即从头到尾循环复用一个数组)
  4. 更新count的值,表示当前队列长度+1。
  5. 调用notEmpty.signal()通知队列非空,消费者可以从队列中获取值了。
private void enqueue(E x) {
       //获取队列底层的数组
        final Object[] items = this.items;
        //将putindex位置的值设置为我们传入的x
        items[putIndex] = x;
        //更新putindex,如果putindex等于数组长度,则更新为0
        if (++putIndex == items.length)
            putIndex = 0;
        //队列长度+1
        count++;
        //通知队列非空,那些因为获取元素而阻塞的线程可以继续工作了
        notEmpty.signal();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

自此我们了解了put方法的流程,为了更加完整的了解ArrayBlockingQueue关于生产者-消费者模型的设计,我们继续看看阻塞获取队列元素的take方法。

take方法的逻辑和put方法是相反的,它的整体执行步骤为:

  1. 上锁,避免获取元素时出现线程安全问题。
  2. 检查队列中的元素的数量,如果为0则说明队列中没有元素,则调用notEmpty.await()打断当前线程,并将其存放到AQS等待队列中等待非空。
  3. 如果count不为0则说明队列中有元素,可以用take方法获取元素,则调用dequeue方法获取元素。
  4. 完成元素获取后释放锁。
public E take() throws InterruptedException {
		//尝试先上锁再获取元素
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        
        try {
        //如果队列中元素个数为0,则将当前线程打断并存入AQS队列中,等待非空条件通知
            while (count == 0)
                notEmpty.await();
         //如果队列不为空则调用dequeue获取元素
            return dequeue();
        } finally {
        //完成操作后释放锁
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

步入查看dequeue方法详情后,我们发现出队的操作和入队差不多:

  1. 将takeIndex元素复制给E引用。
  2. 将数组中takeIndex位置设置为null。
  3. 更新takeIndex的值,即下一次取元素中自增后的takeIndex获取,如果takeIndex为数组长度则将takeIndex更新为0。
  4. count减1,意味从队列中移除一个元素。
  5. 通知那些被阻塞并存放到AQS队列中的线程,当前队列非满,可以继续存放元素了。
private E dequeue() {
      	//获取阻塞队列底层的数组
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //从队列中获取takeIndex位置的元素
        E x = (E) items[takeIndex];
        //将takeIndex置空
        items[takeIndex] = null;
        //takeIndex向后挪动,如果等于数组长度则更新为0
        if (++takeIndex == items.length)
            takeIndex = 0;
        //队列长度减1
        count--;
        if (itrs != null)
            itrs.elementDequeued();
         //通知那些被打断的线程当前队列状态非满,可以继续存放元素
        notFull.signal();
        return x;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

自此我们将阻塞队列的阻塞操作的源码都详细的了解了一遍,我们不妨用一张图来总结一下,两个Condition条件是如何控制ArrayBlockingQueue的存和取的。 我们从消费者开始看起,当消费者从队列中take或者poll等操作取出一个元素之后,就会通知队列非满,此时那些等待非满的生产者就会被唤醒等待获取CPU时间片进行入队操作。 当生产者将元素存到队列中后,就会触发通知队列非空,此时消费者就会被唤醒等待CPU时间片尝试获取元素。如此往复,两个条件对象就构成一个环路,控制着多线程之间的存和取。

在这里插入图片描述

# offer(E e)和poll()方法

有了put和take方法的经验,对于add和remove方法阅读起来就会轻松许多。先来看看offer方法,逻辑和put差不多,唯一的区别就是入队失败时不会阻塞当前线程,而是直接返回false。

public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//队列已满直接返回false
            if (count == items.length)
                return false;
            else {
            //反之将元素入队并直接返回true
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

poll方法同理,获取元素失败也是直接返回空,并不会阻塞获取元素的线程。

public E poll() {
        final ReentrantLock lock = this.lock;
        //上锁
        lock.lock();
        try {
        	//如果队列为空直接返回null,反之出队返回元素值
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11

# offer(E e, long timeout, TimeUnit unit)和poll(long timeout, TimeUnit unit)

因为offer和poll非阻塞的属性,所以设计者同样为其提供了带有等待时间的offer和poll,可以看到带有时间的offer方法在队列已满的情况下,会等待用户所传的时间段,如果规定时间内还不能存放元素则直接返回false。

 public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
        //队列已满,进入循环
            while (count == items.length) {
            //时间到了队列还是满的,则直接返回false
                if (nanos <= 0)
                    return false;
                 //阻塞nanos时间,等待非满
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

同理poll也一样,队列为空则在规定时间内等待,若时间到了还是空的,则直接返回null。

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
        	//队列为空,循环等待,若时间到还是空的,则直接返回null
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# add(E e)和remove()

add方法其实就是对于offer做了一层封装,如下代码所示,可以看到add会调用没有规定时间的offer,如果入队失败则直接抛异常。

public boolean add(E e) {
		//调用下方的add
        return super.add(e);
    }


public boolean add(E e) {
		//调用offer如果失败直接抛出异常
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

remove方法同理,调用poll,如果返回null则说明队列没有东西,直接抛出异常。

public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
1
2
3
4
5
6
7

# ArrayBlockingQueue相关面试题

ArrayBlockingQueue是Java中的阻塞队列实现之一,常用于多线程之间的数据共享。以下是一些关于ArrayBlockingQueue常见的面试题:

ArrayBlockingQueue是什么?它的特点是什么?

答:ArrayBlockingQueue是Java中的阻塞队列实现之一,是一个有界阻塞队列。它的特点是具有固定的容量,当队列已满时,使用put方法继续往里面添加元素的线程会被阻塞,直到队列中有空闲的位置;当队列为空时,使用take方法从队列中取元素的线程会被阻塞,直到队列中有新的元素被添加。

ArrayBlockingQueue和ConcurrentLinkedQueue有什么区别?

答:ArrayBlockingQueue是一个有界阻塞队列,而ConcurrentLinkedQueue是一个无界非阻塞队列。ArrayBlockingQueue的容量是固定的,而ConcurrentLinkedQueue可以动态地增加容量。因为后者是无界链表的特性,在高并发的情况下,ConcurrentLinkedQueue的性能可能会更好,因为它在入队和出队操作时不需要进行线程间的等待和唤醒操作。

ArrayBlockingQueue如何处理生产者-消费者模式?

答:ArrayBlockingQueue可以很好地支持生产者-消费者模式。生产者可以往队列中添加元素,而消费者可以从队列中取出元素进行处理。当队列已满时,生产者线程会被阻塞,直到消费者从队列中取出元素并通知此时队列非满,生产者才能继续存放元素。当队列为空时,消费者线程会被阻塞,直到队列中有新的元素被添加,触发非空条件通知,将消费者唤醒,等待CPU时间片分配到消费者线程再继续消费。

ArrayBlockingQueue如何处理异常情况?

答:当队列已满时,如果使用add方法继续往队列中添加元素,会抛出IllegalStateException异常;当队列为空时,如果使用remove从队列中取元素,会抛出NoSuchElementException异常。同时,如果在等待过程中,线程被中断,也会抛出InterruptedException异常。

ArrayBlockingQueue的实现原理是什么?

答:ArrayBlockingQueue的底层实现是一个数组,通过ReentrantLock实现线程安全,通过Condition实现线程间的等待和唤醒操作。当队列已满时,生产者线程会调用notFull.await()方法让生产者进行等待(即put这种入队操作的线程会被阻塞);当队列为空时,消费者线程会调用notEmpty.await()方法等待队列非空时进行消费。直到队列中有新的元素被添加时,生产者线程会调用notEmpty.signal()方法唤醒消费者线程。同理当队列中有元素被取出时,消费者线程会调用notFull.signal()方法唤醒生产者线程此时队列非满可以继续进行入队操作。

# 参考文献

深入理解Java系列 | BlockingQueue用法详解 (opens new window)

深入浅出阻塞队列BlockingQueue及其典型实现ArrayBlockingQueue (opens new window)

并发编程大扫盲:ArrayBlockingQueue 底层原理和实战 (opens new window)

编辑 (opens new window)
记一个ConcurrentHashMap使用不当导致的并发事故
PriorityQueue源码分

← 记一个ConcurrentHashMap使用不当导致的并发事故 PriorityQueue源码分→

最近更新
01
AI时代专注力培养
04-11
02
DelayQueue源码解析
04-10
03
PriorityQueue源码分
04-10
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×