DelayQueue
DelayQueue是一个内部依靠AQS队列同步器所实现的无界延迟阻塞队列
DelayQueue队列中每个元素都有个过期时间,并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队。
延迟对象需要覆盖 getDelay()与compareTo()方法,并且要注意 getDelay()的时间单位的统一,getDelay定义剩余到期时间,compareTo方法定义了元素排序规则
DelayQueue中内部使用的是PriorityQueue存放数据,使用ReentrantLock实现线程同步,可知是阻塞队列。另外队列里面的元素要实现Delayed接口,一个是获取当前剩余时间的接口,一个是元素比较的接口,因为这个是有优先级的队列。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> { // 持有内部重入锁。 private final transient ReentrantLock lock = new ReentrantLock(); // 优先级队列,存放工作任务。 private final PriorityQueue<E> q = new PriorityQueue<E>(); private Thread leader = null; // 依赖于重入锁的condition。 private final Condition available = lock.newCondition(); }
Thread 指定线程等待在队列头部的元素,成员变量Thead leader设计出来是为了minimize unnecessary timed waiting(减少不必要的等待时间), 在DelayQueue中leader表示一个等待从队列中获取消息的线程。
元素入队
当线程put元素的时候,DelayQueue会对你put的元素通过其本身的compareTo方法进行排序,延时时间越短的顺序越靠近队列头部
/** * Inserts the specified element into this delay queue. * * @param e the element to add * @return {@code true} * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { q.offer(e); if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { lock.unlock(); } }
首先获取独占锁,然后添加元素到优先级队列,由于q是优先级队列,所以添加完元素后,peek()方法返回的并不一定是刚才添加的元素,如果判断为true,说明当前元素e的优先级最小也就是即将过期的,这时候激活avaliable变量条件队列里面的线程,通知它们队列里面有元素。
从队列中取元素
poll()方法取队头当队头元素没过期时返回null,take()方法取队头当队头元素没过期时会一直等待。
当线程take元素的时候,DelayQueue会检测当前是否有Thread已经在等待队头元素,如果有的话,那么只能阻塞当前前程,等已经取到队头的Thread完成以后再唤醒。 如果没有Thread在等待队头元素的话,那么会查询一下队头元素还剩多少Delay时间,并且将当前线程设置为队头等待线程,然后让当前线程wait剩余Delay时间后在来获取队头元素。
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { E first = q.peek(); //如果队列为空,或者不为空但是队头元素没有过期则返回null if (first == null || first.getDelay(NANOSECONDS) > 0) return null; else return q.poll(); } finally { lock.unlock(); } } /** * Retrieves and removes the head of this queue, waiting if necessary * until an element with an expired delay is available on this queue. * * @return the head of this queue * @throws InterruptedException {@inheritDoc} */ 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(NANOSECONDS); if (delay <= 0) return q.poll(); first = null; // don't retain ref while waiting 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(); } }
使用场景
1)关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。
2)缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。
3)任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求。
4)模拟订单自动取消功能。
5)实现延时消息队列(简易版MQ)。
6)缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询。 DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
7)定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现的。
8)典型场景是重试机制实现,比如当调用接口失败后,把当前调用信息放入delay=10s的元素,然后把元素放入队列,那么这个队列就是一个重试队列,一个线程通过take()方法获取需要重试的接口,take()返回则接口进行重试,失败则再次放入队列,同时也可以在元素加上重试次数。
浙公网安备 33010602011771号