ArrayBlockingQueue 学习
原文: https://blog.csdn.net/u014799292/article/details/90167096
在java多线程操作中, BlockingQueue<E> 常用的一种方法之一。在看jdk内部尤其是一些多线程,大量使用了blockinkQueue 来做的。
借用jdk api解释下:
BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:
- 第一种是抛出一个异常,
- 第二种是返回一个特殊值(null 或 false,具体取决于操作),
- 第三种是在操作可以成功前,无限期地阻塞当前线程,
- 第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
抛出异常 | 特殊值 | 阻塞 | 超时 | |
插入 | add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
移除 | remove() |
poll() |
take() |
poll(time, unit) |
检查 | element() |
peek() |
不可用 | 不可用 |
ArrayBlockingQueue介绍
根据 ArrayBlockingQueue 的名字我们都可以看出,它是一个队列,并且是一个基于数组的阻塞队列。ArrayBlockingQueue是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。常用的操作包括 add,offer,put,remove,poll,take,peek。
ArrayBlockingQueue 是一个有界队列,有界也就意味着,它不能够存储无限多数量的对象。所以在创建 ArrayBlockingQueue 时,必须要给它指定一个队列的大小。
我们先来熟悉一下 ArrayBlockingQueue 中的几个重要的方法。
- add(E e):把 e 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则报异常
- offer(E e):表示如果可能的话,将 e 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false
- put(E e):把 e 加到 BlockingQueue 里,如果 BlockQueue 没有空间,则调用此方法的线程被阻断直到 BlockingQueue 里面有空间再继续
- poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,取不到时返回 null
- take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 Blocking 有新的对象被加入为止
- remainingCapacity():剩余可用的大小。等于初始容量减去当前的 size
ArrayBlockingQueue 使用场景。
- 先进先出队列(队列头的是最先进队的元素;队列尾的是最后进队的元素)
- 有界队列(即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作)
- 队列不支持空元素
保存数据的结构
/** The queued items */
final Object[] items;
可以看到,是一个Object的数组。
全局锁
/** The queued items */
final Object[] items;
注视也说明了,这是一个掌管所有访问操作的锁。全局共享。都会使用这个锁。
/** Main lock guarding all access */
final ReentrantLock lock;
ArrayBlockingQueue 进队操作采用了加锁的方式保证并发安全。源代码里面有一个 while() 判断:
public void put(E e) throws InterruptedException {
checkNotNull(e); // 非空判断
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁
try {
while (count == items.length) {
// 一直阻塞,知道队列非满时,被唤醒
notFull.await();
}
enqueue(e); // 进队
} finally {
lock.unlock();
}
}
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 = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
通过源码分析,我们可以发现下面的规律:
- 阻塞调用方式 put(e)或 offer(e, timeout, unit)
- 阻塞调用时,唤醒条件为超时或者队列非满(因此,要求在出队时,要发起一个唤醒操作)
- 进队成功之后,执行notEmpty.signal()唤起被阻塞的出队线程
出队的源码类似。ArrayBlockingQueue 队列我们可以在创建线程池时进行使用。
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(2));
PS:在进行某项业务存储操作时,建议采用offer进行添加,可及时获取boolean进行判断,如用put要考虑阻塞情况(队列的出队操作慢于进队操作),资源占用。
有兴趣的可以顺便了解下 LinkedBlockingQueue
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构