阻塞队列概念及其简单使用
什么是阻塞队列
概念
当队列满的时候,插入元素的线程被阻塞,直到队列不满
队列为空的时候,获取元素的线程被阻塞,直到队列不为空
生产者消费者模式也是阻塞队列的一种体现
常用阻塞队列
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
内容关于:并发编程
本文来源于网络,只做技术分享,一概不负任何责任