概念
当队列满的时候,插入元素的线程被阻塞,直到队列不满
队列为空的时候,获取元素的线程被阻塞,直到队列不为空
生产者消费者模式也是阻塞队列的一种体现
常用阻塞队列
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
DelayQueue:一个使用优先级队列实现的无界阻塞队列
SynchronousQueue:一个不存储元素的阻塞队列
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
常用方法
方法 | 抛出异常 | 返回值 | 一直阻塞 | 超时退出 |
插入方法 | add | offer | put | offer(timeout) |
移除方法 | remove | poll | take | poll(timeout) |
检查方法 | element | peek | N/A | N/A |
介绍:
ArrayBlockingQueue:按照先进先出的原则,初始化需要设置大小
LinkedBlockingQueue:按照先进先出的原则,可以不设置初始大小,不设置,默认就会Integer.MAX_VALUE
区别:
锁:ArrayBlockingQueue,只使用了一把锁,而LinkedBlockingQueue使用了2把
实现:ArrayBlockingQueue直接插入元素,LinkedBlockingQueue需要转换
初始化:ArrayBlockingQueue必须指定初始化大小,LinkedBlockingQueue可以不指定
PriorityBlockingQueue:默认采用自然顺序排序,就1<2,A>B... ,如果想自定义顺序有要么实现CompateTo方法,要么指定构造参数Comparator,如果一致,PriorityBlockingQueue不保证优先级顺序
DelayQueue:支持延时获取元素的阻塞队列,内部使用PriorityBlockingQueue,元素必须实现Delayed接口才允许放入
SynchronousQueue:每一个put操作,都要等待一个take操作,类似于等待唤醒机制
LinkedTransferQueue: 相比于LinkedBlockingQueue多了两个方法,transfer和tryTransfer,transfer在往队列里面插入元素之前,先看一下有没有消费者在等着,如果有,直接把元素交给消费者,省去了插入和,取出的步骤,tryTransfer尝试把元素给消费者,无论消费者是否接收,都会立即返回,transfer必须要消费者消费之后,才会返回
LinkedBlockingDeque:可以从队列的头部和尾部都可以插入和移除元素,可以在有竞争的时候从两侧获取元素,减少一半的时间,在ForkJoin中的工作密取机制就是采用的LinkedBlockingDeque实现的,凡是方法名带了First的都是从头去拿,带了Last都是从尾部拿,不加的话,默认add等于addLast,remove等于removeFirst,take方法等于takeFirst
建议:尽量采用有界阻塞队列, 因为在流量高峰的时候,无界阻塞队列会不断的增加占用资源,可能导致服务器宕机
案例:
使用DelayQueue实现延时订单功能
定义元素容器类
package org.dance.day5.bq;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 存放到队列的元素
* @author ZYGisComputer
*/
public class ItemVo<T> implements Delayed {
/**
* 延时时间 单位为毫秒
*/
private long activeTime;
private T data;
public ItemVo(long activeTime, T data) {
// 将时间转换为纳秒 并 + 当前纳秒 = 将超时时长转换为超时时刻
this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime,TimeUnit.MILLISECONDS) + System.nanoTime();
this.data = data;
}
/**
* 返回元素的剩余时间
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
}
/**
* 按照剩余时间排序
* @param o
* @return
*/
@Override
public int compareTo(Delayed o) {
long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return (d==0)?0:(d>0)?1:-1;
}
public long getActiveTime() {
return activeTime;
}
public T getData() {
return data;
}
}
定义订单实体类
package org.dance.day5.bq;
/**
* 订单实体类
* @author ZYGisComputer
*/
public class Order {
private final String orderNo;
private final double orderMoney;
public Order(String orderNo, double orderMoney) {
this.orderNo = orderNo;
this.orderMoney = orderMoney;
}
public String getOrderNo() {
return orderNo;
}
public double getOrderMoney() {
return orderMoney;
}
}
定义订单放入线程
package org.dance.day5.bq;
import java.util.concurrent.DelayQueue;
/**
* 将订单放入队列
* @author ZYGisComputer
*/
public class PutOrder implements Runnable{
private DelayQueue<ItemVo<Order>> queue;
public PutOrder(DelayQueue<ItemVo<Order>> queue) {
this.queue = queue;
}
@Override
public void run() {
// 5秒到期
Order order = new Order("1",16.00d);
// 包装成ItemVo
ItemVo<Order> itemVo = new ItemVo<>(5000,order);
queue.offer(itemVo);
System.out.println("订单5秒后到期:"+order.getOrderNo());
// 5秒到期
order = new Order("2",18.00d);
// 包装成ItemVo
itemVo = new ItemVo<>(8000,order);
queue.offer(itemVo);
System.out.println("订单8秒后到期:"+order.getOrderNo());
}
}
定义订单消费线程
package org.dance.day5.bq;
import java.util.concurrent.DelayQueue;
/**
* 获取订单
*
* @author ZYGisComputer
*/
public class FetchOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
public FetchOrder(DelayQueue<ItemVo<Order>> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
ItemVo<Order> poll = null;
try {
poll = queue.take();
Order data = poll.getData();
String orderNo = data.getOrderNo();
System.out.println(orderNo + "已经消费");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
}
测试类
package org.dance.day5.bq;
import java.util.Queue;
import java.util.concurrent.DelayQueue;
/**
* @author ZYGisComputer
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
DelayQueue<ItemVo<Order>> queue = new DelayQueue<>();
new Thread(new PutOrder(queue)).start();
new Thread(new FetchOrder(queue)).start();
for (int i = 0; i < 15; i++) {
Thread.sleep(500);
System.out.println(i*500);
}
}
}
执行结果
订单5秒后到期:1
订单8秒后到期:2
0
500
1000
1500
2000
2500
3000
3500
4000
4500
1已经消费
5000
5500
6000
6500
7000
2已经消费
通过执行结果分析,可以得知,两个放入阻塞队列的元素已经成功被消费掉了,当然这种阻塞队列也可以用于别的场景,比如实现本地延时缓存,限时缴费....
作者:彼岸舞
时间:2021\01\11
内容关于:并发编程
本文来源于网络,只做技术分享,一概不负任何责任