DelayQueue源码解析

时间:2022-07-23
本文章向大家介绍DelayQueue源码解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

DelayQueue按照字面意思就是延迟队列。那么这里的延迟是什么意思?如果按照之前的学习,队列都是先进先出的。那么延迟难道是如果空间不足的时候先延迟一下等到队列有空间了再进行相关的写操作?怀着这样的想法,咋慢慢的进行探索。

我们还是先看一下DelayQueue的类结构。

我们看到,DelayQueue的代码量还是比较少的。长舒一口气

而且,参数也贼少。好开心呀!那么我们来看一下这些擦输,lock没有什么疑问,就是重入锁ReetrantLock了,那么q是做什么的?根据类名称,大概知道这是一个优先队列,也就是说其中的元素具有一定的优先级。往往是优先级比较高的元素会被选中的意思吧?先在这里打个问号。后边说不定会醍醐灌顶一下。leader是一个线程,目前还不清楚是做什么的,available当然也是一脸懵逼了。好了咋还是看源码吧。

在上边的类中,E extends delayed,意思就是说我们传入的参数,都默认具有delayed的属性。但是没有找到这个方法是如何实现的。查阅了相关资料,说这个属性就是在给你的数据元素添加了时间戳一样的东西,然后和过期时间绑定。也就是先过期的会排在前边,后过期的会排在后边。

private final transient ReentrantLock lock = new ReentrantLock();

private final PriorityQueue<E> q = new PriorityQueue<E>();

private Thread leader = null;

private final Condition available = lock.newCondition();

首先我们还是先看看优先级队列PriorityQueue的源码吧,毕竟延迟队列都是基于优先级队列写的。

方法很多,但是类的继承关系比较单纯。用一句话就是优先级队列就是想好好做队列,不像上一篇分析ArrayBlockingQueue那么社会。

在优先级队列中,我们看到默认的容量是11,为啥是11里?这东西一般都和扩容有关系

通过查看优先级队列的初始化方法,发现该队列的优先级其实就是用户自定义的Compareter实体。

对于队列的扩容

如果容量比较小的话就给双倍,否则的话就50%的进行扩容。如果超出了最大限度,那么就直接以最大值作为容量。

在添加元素的时候

如果空间不够就进行扩容,然后进行数据入列。之后使用compareter进行排序。这里还是不看排序的代码了,我们大概知道代码做了什么事情就足够了。

解析完毕优先级队列,下面就开始我们的正事---DelayQueue解析

通过上边对优先级队列的学习,我们大概知道延迟队列中的优先级队列里边都是什么鬼了。那么我就看看延迟队列如何玩这个优先级队列的。

果然,由于家有点东西的不一般啊,这没啥大惊小怪的。我们继续。

在插入元素的时候,就直接找优先级队列了。如果是第一个元素的话,那么就把leader置为null,否则直接返回true表示插入成功。其中put方法也是这个逻辑过程。

在poll方法中

也是直接操作,因为优先级队列有顺序,所以看来一切都是那么辣。

在take方法中,使用和其他阻塞一样的方式。

先加锁,然后一直检测队列是否有值,如果没值的话就让自己等待,知道有值了,再进行获取值得操作。可以看到这里的leader就是一个标志。就是线程进入临界区的一个标识。这里看一下,那个if语句,当多个线程进行peek的时候,然后同时都加锁,然后如果当前线程的leader为空,那么说明已经有线程获已经在获取了,那么当前线程就等待。如果为空说明没有其他线程,那么当前线程就去取。

总结:

通过上述分析,DelayQueue的延迟是指的delayed的接口,也就是按到期时间进行peek的操作。才能够拿到值,除此之外,获取队列中的元素的时候采用的是阻塞的方式。

在实践中,如果对时间要求比较严格的话,可以采用延迟队列,比如缓存之类的,可以通过设置缓存的过期时间,然后让消费者线程进行回收,因为所有操作缓存的写操作都是加锁的,因此不存在脏数据的情况。