long getDelay(TimeUnit unit);
}
Delayed是一个继承自Comparable的接口,并且定义了一个getDelay()方法,用于表示还有多少时间到期,到期了应返回小于等于0的数值。
public class DelayQueue extends AbstractQueue
implements BlockingQueue {
//可重入锁
private final transient ReentrantLock lock = new ReentrantLock();
//存储元素的优先级队列
private final PriorityQueue q = new PriorityQueue();
//获取数据 等待线程标识
private Thread leader = null;
//条件控制,表示是否可以从队列中取数据
private final Condition available = lock.newCondition();
}
-
lock:全局独占锁,用于实现线程安全
-
q:优先队列,用于存储元素,并按优先级排序
-
leader:用于优化内部阻塞通知的线程
-
available:用于实现阻塞的Condition对象
其实看到这里,我们应该已经能够了解DelayQueue的大致实现思路了:
以支持优先级的PriorityQueue无界队列作为一个容器,因为元素都必须实现Delayed接口,可以根据元素的过期时间来对元素进行排列,因此,先过期的元素会在队首,每次从队列里取出来都是最先要过期的元素。
1、构造方法
// 默认构造方法,这个简单,什么都没有
public DelayQueue() {}
// 通过集合初始化
public DelayQueue(Collection<? extends E> c) {
this.addAll©;
}
DelayQueue 内部组合PriorityQueue,对元素的操作都是通过PriorityQueue 来实现的,DelayQueue 的构造方法很简单,对于PriorityQueue 都是使用的默认参数,不能通过DelayQueue 来指定PriorityQueue的初始大小,也不能使用指定的Comparator,元素本身就需要实现Comparable ,因此不需要指定的Comparator。
2、入队
(1)、add(E e)
将指定的元素插入到此队列中,在成功时返回 true
public boolean add(E e) {
return offer(e);
}
(2)、offer(E e)
将指定的元素插入到此队列中,在成功时返回 true,在前面的add 中,内部调用了offer 方法,我们也可以直接调用offer 方法来完成入队操作。
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
// 获取锁
lock.lock();
try {
// 向 PriorityQueue中插入元素
q.offer(e);
// 如果当前元素的队首元素(优先级最高),leader设置为空,唤醒所有等待线程
if (q.peek() == e) {
leader = null;
available.signal();
}
// 无界队列,永远返回true
return true;
} finally {
lock.unlock();
}
}
peek并不一定是当前添加的元素,队头是当前添加元素,说明当前元素e的优先级最小也就即将过期的,这时候激活avaliable变量条件队列里面的一个线程,通知他们队列里面有元素了。
leader是等待获取队列头元素的线程,应用主从式设计减少不必要的等待。如果leader不等于空,表示已经有线程在等待获取队列的头元素。所以,通过await()方法让出当前线程等待信号。如果leader等于空,则把当前线程设置为leader,当一个线程为leader,它会使用awaitNanos()方法让当前线程等待接收信号或等待delay时间。
(3)、offer(E e, long timeout, TimeUnit unit)
public boolean offer(E e, long timeout, TimeUnit unit) {
//调用offer 方法
return offer(e);
}
因为是无界队列,因此不会出现”队满”(超出最大值会抛异常),指定一个等待时间将元素放入队列中并没有意义,队列没有达到最大值那么会入队成功,达到最大值,则失败,不会进行等待。
(4)、put(E e)
将指定的元素插入此队列中,队列达到最大值,则抛oom异常
public void put(E e) {
offer(e);
}
虽然提供入队的接口方式很多,实际都是调用的offer 方法,通过PriorityQueue 来进行入队操作,入队超时方法并没有其超时功能。
3、出队
(1)、poll()
获取并移除此队列的头,如果此队列为空,则返回 null
public E poll() {
final ReentrantLock lock = this.lock;
//获取同步锁
lock.lock();
try {
//获取队头
E first = q.peek();
//如果队头为null 或者 延时还没有到,则返回null
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll(); //元素出队
} finally {
lock.unlock();
}
}
(2)、poll(long timeout, TimeUnit unit)
获取并移除此队列的头部,在指定的等待时间前等待。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
//超时等待时间
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//可中断的获取锁
lock.lockInterruptibly();
try {
//无限循环
for (;😉 {
//获取队头元素
E first = q.peek();
//队头为空,也就是队列为空
if (first == null) {
//达到超时指定时间,返回null
if (nanos <= 0)
return null;
else
// 如果还没有超时,那么再available条件上进行等待nanos时间
nanos = available.awaitNanos(nanos);
} else {
//获取元素延迟时间
long delay = first.getDelay(NANOSECONDS);
//延时到期
if (delay <= 0)
return q.poll(); //返回出队元素
//延时未到期,超时到期,返回null
if (nanos <= 0)
return null;
first = null; // don’t retain ref while waiting
// 超时等待时间 < 延迟时间 或者有其它线程再取数据
if (nanos < delay || leader != null)
//在available 条件上进行等待nanos 时间
nanos = available.awaitNanos(nanos);
else {
//超时等待时间 > 延迟时间 并且没有其它线程在等待,那么当前元素成为leader,表示leader 线程最早 正在等待获取元素
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//等待 延迟时间 超时
long timeLeft = available.awaitNanos(delay);
//还需要继续等待 nanos
nanos -= delay - timeLeft;
} finally {
//清除 leader
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//唤醒阻塞在available 的一个线程,表示可以取数据了
if (leader == null && q.peek() != null)
available.signal();
//释放锁
lock.unlock();
}
}
来梳理梳理这里的逻辑:
1、如果队列为空,如果超时时间未到,则进行等待,否则返回null
2、队列不空,取出队头元素,如果延迟时间到,则返回元素,否则 如果超时 时间到 返回null
3、超时时间未到,并且超时时间< 延迟时间或者有线程正在获取元素,那么进行等待
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
结尾
这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。
由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
大厂面经、学习笔记、源码讲义、实战项目、讲解视频**
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-QouCJoa9-1711179293286)]
结尾
[外链图片转存中…(img-zkwr6rGi-1711179293287)]
这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。
由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!