DelayQueue,顾名思义,是具有延迟功能的队列,名字没有表现出来的还有一个功能就是优先级队列,它的实现依赖了我们熟知的PriorityQueue,很棒哈。当然,想进入这个队列的元素自身必须先实现Delayed接口和Comparable接口,她只有两个构造函数,默认的就不说了,另一个就是参数为Collection的构造函数,但是你可以通过这个Collection传给你想放在DelayQueue的东西(内容很丰富,代表你可以利用PriorityQueue的那些构造函数,,,好了,这些东西有点基础的人都自己会还用我说什么!)
下面说说里面一些我认为比较重要的点,大家编程中留心这些知识点。
DelayQueue利用PriorityQueue存储自身队列数据,这一点表明他拥有PriorityQueue的很多特性。 1,长度无限长,无offer阻塞; 2,有take阻塞,但是在PriorityQueue基础上又添加了等待线程Thread header,为什么会有这个属性,这就和DelayQueue自身的作用紧密相关了。因为要实现delay效果又要实现并发,必然带来队列存取的同步问题,你可能说不是有lock来保证同步吗?但是别忘了,他们有“取”延时功能,在延时的过程中可能会有其他线程来取队列元素,下给出取元素代码,方便解释
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null)
available.await();
else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)
return q.poll();
else if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
看到没,在验证队列是否为空时,使用的是PriorityQueue.peek()这个函数很普通并没有对队列进行同步操作,这也导致一个线程在available.awaitNanos(delay);时进入等待状态时,别的线程也有可能调用此函数,而再次进行队列是否为空判定,但是上一个线程并没有取出元素导致这一个线程在使用PriorityQueue.peek())时也会得到队列非空结果,也会进入available.awaitNanos(delay);状态,那么如果没有一些特殊的处理操作肯定会出错,于是这个等待线程标志header就出现了。他很巧妙地实现了这样的功能:会让先运行的“取”线程在等待后先“取”元素,而且是安全取,之后的线程要想“取”必须等待队列非空。这里面还有一点很重要,如果你出于什么目的想要扩展我们的DelayQueue在里面加一些唤醒操作你一定要注意DelayQueue的这一特性,即:我们的所有的等待“取”元素的线程均是由等待“取”线程的队首线程唤醒的!!所以你自己如果随意去使用Condition.signal()去唤醒等待的线程很可能会导致有些“取”线程永远死在那个等待“取”状态(又想多一嘴,由于这个特性,每次“取”并不是真正按着从“小”到“大”的顺序来的,而是部分按着PriorityQueue的方式从“小”到“大”取得)。
当然你可能有更好的方法实现完全按着从小到大“取”的方法,这个根据你的业务需求,不太严格的情况下这个蛮不错的,如果你实现完全从小到大,我想带来的肯定就是性能问题了
就到这儿了
DelayQueue要点解析
最新推荐文章于 2025-02-20 21:24:44 发布