
在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
什么是阻塞队列
1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。
常用阻塞队列
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
有界无界?
有限队列就是长度有限,满了以后生产者会阻塞,无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞,当然空间限制来源于系统资源的限制,如果处理不及时,导致队列越来越大越来越大,超出一定的限制致使内存超限,操作系统或者JVM 帮你解决烦恼,直接把你OOM kill 省事了。
ArrayBlockingQueue
是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置
LinkedBlockingQueue
是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
Array 实现和Linked 实现的区别?
- 队列中锁的实现不同
ArrayBlockingQueue 实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;
LinkedBlockingQueue 实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock - 在生产或消费时操作不同
ArrayBlockingQueue 实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;
LinkedBlockingQueue 实现的队列中在生产和消费的时候,需要把枚举对象转换为Node进行插入或移除,会影响性能 - 队列大小初始化方式不同
ArrayBlockingQueue 实现的队列中必须指定队列的大小;
LinkedBlockingQueue 实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE
DelayQueue
是一个支持延时获取元素的无界阻塞队列。
DelayQueue 非常有用,可以将DelayQueue 运用在以下应用场景。
缓存系统的设计:可以用DelayQueue 保存缓存元素的有效期,使用一个线
程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期
到了。还有订单到期,限时支付等等
使用了等待通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用
DelayQueue的应用
- 定义对象包装类
public class ItemVo<T> implements Delayed {
//到期时间,但传入的数值代表过期的时长,传入单位毫秒
private long activeTime;
private T data;//业务数据,泛型
//传入过期时长,单位秒,内部转换
public ItemVo(long expirationTime, T data) {
this.activeTime = expirationTime*1000+System.currentTimeMillis();
this.data = data;
}
public long getActiveTime() {
return activeTime;
}
public T getData() {
return data;
}
/*
* 这个方法返回到激活日期的剩余时间,时间单位由单位参数指定。
*/
public long getDelay(TimeUnit unit) {
long d = unit.convert(this.activeTime - System.currentTimeMillis(),unit);
return d;
}
/*
*Delayed接口继承了Comparable接口,按剩余时间排序,实际计算考虑精度为纳秒数
*/
public int compareTo(Delayed o) {
long d = (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
if (d==0){
return 0;
}else{
if (d<0){
return -1;
}else{
return 1;
}
}
}
}
- 定义订单实体
public class Order {
private final String taNo;//订单的编号
public Order(String taNo) {
super();
this.taNo = taNo;
}
public String getTaNo() {
return taNo;
}
}
- 业务代码中定义生产者,45分钟订单超时取消
/** 放入延时队列 超过45分钟订单自动取消*/
Order orderTb = new Order(taNo);
ItemVo<Order> itemTb = new ItemVo<Order>(45 * 60,orderTb);
FetchOrder.queue.offer(itemTb);
- 定义消费者,实现ApplicationRunner接口,实现启动加载
@Component
public class FetchOrder implements ApplicationRunner {
@Autowired
private ItineraryMapperCustom itineraryMapperCustom;
//订单延时队列
public static DelayQueue<ItemVo<Order>> queue = new DelayQueue<ItemVo<Order>>();
@Override
public void run(ApplicationArguments args) throws Exception {
while(true) {
try {
ItemVo<Order> item = queue.take();
Order order = (Order)item.getData();
System.out.println("taNo :"+ order.getTaNo());
System.out.println("需要完成订单取消"+ order.getTaNo());
itineraryMapperCustom.updateStatus(order.getTaNo(), OrderEnum.CANCELED.getType());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
当然存在缺点:单机、不能持久化、宕机任务丢失等。
还有其他方案:
- 最简单的方式,定时扫表
- 使用RabbitMq 实现 RabbitMq实现延迟队列
- 基于Redis自研延迟队列
本文详细解析了阻塞队列的概念,包括其工作原理、主要类型如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等,并探讨了它们在并发编程中的应用及优缺点。
6872

被折叠的 条评论
为什么被折叠?



