DelayQueue 源码分析
本文参考了 https://www.cnblogs.com/myseries/p/10944211.html
一、DelayQueue是什么
DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。
二、DelayQueue能做什么
1. 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。
2. 饿了吗订餐通知:下单成功后60s之后给用户发送短信通知。
3.关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。
4.缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。
5.任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。
三 代码示例
DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象,意思是说我们不能想往DelayQueue里添加什么就添加什么,不能添加int、也不能添加String进去,必须添加我们自己的实现了Delayed接口的类的对象
class MyDelayedTask implements Delayed{ private String name ; private long start = System.currentTimeMillis(); private long time ; public MyDelayedTask(String name,long time) { this.name = name; this.time = time; } /** * 需要实现的接口,获得延迟时间 用过期时间-当前时间 * @param unit * @return */ @Override public long getDelay(TimeUnit unit) { return unit.convert((start+time) - System.currentTimeMillis(),TimeUnit.MILLISECONDS); } /** * 用于延迟队列内部比较排序 当前时间的延迟时间 - 比较对象的延迟时间 * @param o * @return */ @Override public int compareTo(Delayed o) { MyDelayedTask o1 = (MyDelayedTask) o; return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));//比较方法必须要和getDelay的逻辑一直,就是说时间越小的优先级也是越高的 } @Override public String toString() { return "MyDelayedTask{" + "name='" + name + '\'' + ", time=" + time + '}'; } }
private static DelayQueue delayQueue = new DelayQueue(); public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { delayQueue.offer(new MyDelayedTask("task1",10000)); delayQueue.offer(new MyDelayedTask("task2",3900)); delayQueue.offer(new MyDelayedTask("task3",1900)); delayQueue.offer(new MyDelayedTask("task4",5900)); delayQueue.offer(new MyDelayedTask("task5",6900)); delayQueue.offer(new MyDelayedTask("task6",7900)); delayQueue.offer(new MyDelayedTask("task7",4900)); } }).start(); while (true) { Delayed take = delayQueue.take(); System.out.println(take); } }
四 源码分析
关键的成员变量
private final transient ReentrantLock lock = new ReentrantLock();//同步工具 private final PriorityQueue<E> q = new PriorityQueue<E>();//内部使用了PriorityQueue private Thread leader = null;//第一个等待的线程 private final Condition available = lock.newCondition();//负责阻塞
leader是第一个等待的线程,只凭这句话不太好理解,下文分析poll的时候会进一步讲解
入队
public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { q.offer(e);//调用底层的offer方法 if (q.peek() == e) {//如果入队成功了 leader = null;//leader置空 available.signal();//通知condition } return true; } finally { lock.unlock(); } }
出队
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { E first = q.peek(); if (first == null || first.getDelay(NANOSECONDS) > 0)//如果delay大于0 就是说还没到时间,那就直接返回null return null; else return q.poll();//返回底层的poll } finally { lock.unlock(); } }
代码并不复杂,主要逻辑就是判断delay时间到没到,没到的话直接返回null
相对复杂的方法是这个
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) { if (nanos <= 0) return null; else nanos = available.awaitNanos(nanos);//停止timeout指定的时间 } else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll();//如果delay小于0,直接返回值 if (nanos <= 0) return null;//拉取等待时间小于等于0,就返回null first = null; // don't retain ref while waiting 这里我理解是为了释放强引用 if (nanos < delay || leader != null)//leader是第一个等待的线程,也就是说如果delay到了,优先给他。nanos < delay 说明等待到了delay还到不了,那就阻塞等待 nanos = available.awaitNanos(nanos);//注意这里的返回值,返回值代表的是awaitNanos等待的剩余时间,
比如如果没有中断那么这个值大概<=0,如果等待被中断那就是0<nanos< 之前的nanos,leader !=null 那么下一次自旋该线程就有机会拿到数据了
else { Thread thisThread = Thread.currentThread();//上面的if没拦住的话说明delay<nanos同时leader==null,那也就是说在等待期间是能够拿到数据的 leader = thisThread;//优先把数据给他 try { long timeLeft = available.awaitNanos(delay);//等待delay这么久,不出异常就应该拿到数据了 timeLeft大概 <=0 nanos -= delay - timeLeft;//剩余的等待时间 } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
浙公网安备 33010602011771号