JUC的六大阻塞队列BlockingQueue如何实现处理?
摘要:你如果还不了解Java 21的六大BlockingQueue阻塞队列,那么看这篇文章就够了。我会介绍阻塞队列的定义、种类、实现原理以及应用。
摘要:你如果还不了解Java 21中BlockingQueue的六大阻塞队列,那么看这篇文章就够了。我会介绍阻塞队列的定义、种类、实现原理以及应用。
JUC干货系列目录:
JAVA JUC干货之线程池状态和状态切换
JAVA JUC干货之线程池实现原理和源码详解(上)
JAVA JUC干货之线程池实现原理和源码详解(下)
JAVA JUC干货之六大阻塞队列BlockingQueue
综述
大家如果看完上述【JAVA JUC干货系列】文章,相信对线程池的理解会更丝滑流畅,例如java中的线程池由下列四个核心组件组成——线程池管理器ThreadPoolExecutor、用作缓冲区的任务队列、创建新线程的线程工厂和拒绝策略等。本文就在上述博客的基础上,分享线程池核心组件任务队列的基本概念、常用种类、实现原理以及应用。肝文不易,看完别忘了点赞关注哦。
在多线程编程领域,所谓阻塞,是指在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤醒。阻塞队列中的“阻塞”也是这个意思。
熟悉消息队列(MessageQueue)八股文的老铁一定知道消息队列有解耦、异步处理、提高系统可扩展性和削峰填谷神奇效果。同样阻塞队列BlockingQueue的作用也包含这四种,区别是BlockingQueue只作用于本机器,而消息队列相当于分布式BlockingQueue。
为什么需要阻塞队列?它在消息传输过程中充当临时保存消息的容器,是实现生产者-消费者模型等常见并发模式的重要工具。当系统中出现“生产”和“消费”的速度不一致或稳定性等影响系统健壮性因素的时候,就需要阻塞队列削峰填谷,作为抽象层,能够有效地平衡生产者和消费者之间的速度差异,提供一种平滑和安全的数据交互方式。
Java 中的线程池使用了两种类型的任务队列:有界队列和无界队列。有界队列可以限制任务队列的最大长度,控制待处理任务的数量;而无界队列则没有长度限制,可以永无止境地向其提交新任务。
BlockingQueue核心操作方法
本章节介绍阻塞队列常用的方法及其行为。
remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常。
element():返回队列头部元素但不移除,如果队列为空,抛出异常。
peek():返回队列头部元素但不移除,如果队列为空,返回 null。
下面根据插入和获取对操作方法进行分类介绍。
插入数据
add(E e) 向队列尾部写入新的数据e,如果插入成功,则返回true;如果队列已满则插入失败,抛出队列已满的异常。
offer(E e):表示如果可能的话,将新数据写入队列尾部,即如果BlockingQueue有空间,则插入成功并返回true;否则,因队列已满而插入失败,返回false。本方法不阻塞当前执行方法的线程。
offer(E e, long timeout, TimeUnit unit):添加元素到队列中,如果队列满了返回 false。可以设定等待插入元素的时间,如果在指定的时间内还不能加入,则抛出IllegalStateException(“Queue full”)异常。
put(E e):添加元素到队列中,如果队列满了则调用此方法的线程被挂起,直到队列有空间再继续。
获取数据
poll():从队列中移除并获取位于队首的元素,若成功,则返回队首元素;如果队列为空则返回 null。
poll(long time):取走排在队列首位的对象。若队列为空不能立即取出元素,则可以等待time参数规定的时间, 超时仍然取不到时返回null;否则,返回取得的元素。
poll(long timeout, TimeUnit unit):从队列头部获取数据并且该数据会从队列头部移除,如果队列为空则当前线程会阻塞指定的时间,直到在此期间有新的数据写入,或者阻塞的当前线程被其它线程中断,当线程由于超时退出阻塞时,返回值为null。
take():从队列头部获取排在首位的对象并且该数据会从队列头部移除,如果队列没有任何元素则阻塞,直到队列中有元素被加入。
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),
通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
peek():获取队首元素,若成功,则返回队首元素;否则,返回null。它只查看但不移除队列中的元素。
