并发编程 - 阻塞队列BlockingQueue

并发编程 - 阻塞队列BlockingQueue_第1张图片
在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。

什么是阻塞队列

1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。

常用阻塞队列

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  3. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  5. SynchronousQueue:一个不存储元素的阻塞队列。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

有界无界?

有限队列就是长度有限,满了以后生产者会阻塞,无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞,当然空间限制来源于系统资源的限制,如果处理不及时,导致队列越来越大越来越大,超出一定的限制致使内存超限,操作系统或者JVM 帮你解决烦恼,直接把你OOM kill 省事了。

ArrayBlockingQueue
是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置

LinkedBlockingQueue
是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。

Array 实现和Linked 实现的区别?

  1. 队列中锁的实现不同
    ArrayBlockingQueue 实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;
    LinkedBlockingQueue 实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock
  2. 在生产或消费时操作不同
    ArrayBlockingQueue 实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;
    LinkedBlockingQueue 实现的队列中在生产和消费的时候,需要把枚举对象转换为Node进行插入或移除,会影响性能
  3. 队列大小初始化方式不同
    ArrayBlockingQueue 实现的队列中必须指定队列的大小;
    LinkedBlockingQueue 实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE

DelayQueue
是一个支持延时获取元素的无界阻塞队列。
DelayQueue 非常有用,可以将DelayQueue 运用在以下应用场景。
缓存系统的设计:可以用DelayQueue 保存缓存元素的有效期,使用一个线
程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期
到了。还有订单到期,限时支付等等

使用了等待通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用

DelayQueue的应用

  1. 定义对象包装类
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;
			}
		}
	}

}

  1. 定义订单实体
public class Order {
	private final String taNo;//订单的编号

	public Order(String taNo) {
		super();
		this.taNo = taNo;
	}

	public String getTaNo() {
		return taNo;
	}
}
  1. 业务代码中定义生产者,45分钟订单超时取消
/** 放入延时队列 超过45分钟订单自动取消*/
Order orderTb = new Order(taNo);
ItemVo<Order> itemTb = new ItemVo<Order>(45 * 60,orderTb);
FetchOrder.queue.offer(itemTb);
  1. 定义消费者,实现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();
			}
		}
		
	}	
}

当然存在缺点:单机、不能持久化、宕机任务丢失等。
还有其他方案:

  1. 最简单的方式,定时扫表
  2. 使用RabbitMq 实现 RabbitMq实现延迟队列
  3. 基于Redis自研延迟队列

你可能感兴趣的:(并发编程)