JUC中的阻塞队列
JUC中的阻塞队列
阻塞情况
阻塞队列中,线程阻塞有这样的两种情况:
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞,直到有数据放入队列。
- 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞,直到队列中有空的位置,线程被自动唤醒。
如下两图:
空的情况
满的情况
BlockingQueue接口
BlockingQueue的主要方法
官方提供的方法表格
Throws exception | Special value | Blocks | Times out | |
---|---|---|---|---|
Insert | add(e) | offer(e) | put(e) | offer(e, time, unit) |
Remove | remove() | poll() | take() | poll(time, unit) |
Examine | element() | peek() | not applicable | not applicable |
插入
- public abstract boolean add(E paramE):将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。如果该元素是 NULL,则会抛出 NullPointerException 异常。
- public abstract boolean offer(E paramE):将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false。
- public abstract void put(E paramE) throws InterruptedException: 将指定元素插入此队列中,将等待可用的空间(如果有必要)
- offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入 BlockingQueue,则返回失败。
获取
- poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,取不到时返回 null;
- poll(long timeout, TimeUnit unit):从 BlockingQueue 取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则直到时间超时还没有数据可取,返回失败。
- take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 BlockingQueue 有新的数据被加入。
- drainTo():一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
BlockingQueue接口的实现类
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
- ArrayBlockingQueue :用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。
- LinkedBlockingQueue(双锁提高并发) :基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,此队列按照先进先出(FIFO)的原则对元素进行排序。 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
- PriorityBlockingQueue :是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现compareTo()方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue 时,指定构造参数 Comparator 来对元素进行排序。
- DelayQueue:是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实现 Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
- SynchronousQueue:是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。
- LinkedTransferQueue:相 对 于 其 他 阻 塞 队 列 ,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
- LinkedBlockingDeque:是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。
ArrayBlockingQueue
ArrayBlockingQueue实现了BlockingQueue接口,所以拥有BlockingQueue的所有方法 ,看下面代码时对照着上面的表看即可。
用ArrayBlockingQueue模仿生成者和消费者,生产者分别是A,B,C,其中A,C使用了add()方法添加元素,add()会抛出异常,所以做了处理,而B使用put()方法添加元素,队列满了也不会抛出队列满的异常,自会进行阻塞。消费者D负责那东西,使用remove()方法,remove()方法会抛异常,所以我这里在拿之前判断检测了一下队列是否为空。
当然你也可以试试其他方法获取队列元素,或者填充队列,就看你是那种需求,要阻塞的还是抛出异常的,还是放回true或false的。
public class ArrayblockingTest {
//阻塞队列
private ArrayBlockingQueue<String> queue;
public ArrayblockingTest(){
queue = new ArrayBlockingQueue<String>(8);
}
//添加元素,队列满了的话,会抛异常的
public void additem(String str){
this.queue.add(str);
}
//put本身不抛异常,但有个InterruptedException要捕获
public void putitem(String str){
try {
this.queue.put(str);
//这里处理一下中断异常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//会抛异常
public String removeitem(){
return this.queue.remove();
}
//不抛异常
public boolean isempty(){
return this.queue.peek() == null;
}
public static void main(String[] args) {
ArrayblockingTest arrayblocking = new ArrayblockingTest();
//生产者A
new Thread(()->{
int i = 5;
while (i>0)
try {
//这里捕获抛出的异常,并处理
arrayblocking.additem(Thread.currentThread().getName());
i--;
} catch (Exception e) {
System.out.println("队列已经满了无法填充");
continue;
}
},"A").start();
//生产者B
new Thread(()->{
int i = 5;
while (i>0){
i--;
//无异常抛出但会阻塞
arrayblocking.putitem(Thread.currentThread().getName());
}
},"B").start();
//生产者C
new Thread(()->{
int i = 5;
while (i>0)
try {
//这里捕获抛出的异常,并处理
arrayblocking.additem(Thread.currentThread().getName());
i--;
} catch (Exception e) {
System.out.println("队列已经满了无法填充");
continue;
}
},"C").start();
//消费者D
new Thread(()->{
while (true) {
if (!arrayblocking.isempty()) {
System.out.println(arrayblocking.removeitem());
}
else {
System.out.println("当前队列空");
try {
//睡一会,等填满
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"D").start();
}
}
SynchronousQueue
是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。
public class SynblockingTest {
private SynchronousQueue<String> synqueue;
public SynblockingTest(){
synqueue = new SynchronousQueue<>();
}
public void put(String str){
try {
synqueue.put(str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String take(){
try {
return this.synqueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
SynblockingTest syn = new SynblockingTest();
//生产,每1秒生产一个
new Thread(()->{
while (true){
syn.put(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//消费,不断消费,但因为生产者每一秒生产一个,
//所以只能能生产者生产一个才能拿一个
new Thread(()->{
while (true)
System.out.println(Thread.currentThread().getName()+"获得:"+syn.take());
},"B").start();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)