DelayQueue学习及实现定时任务
DelayQueue概念
DelayQueue是一个无界的BlockingQueue(阻塞队列),队列中的元素是以到期时间进行排序的,只有到期的元素才能被取出。
扩展
-
无界队列:
简单来讲,无界队列就是指,当队列满了之后,如果又新增元素,队列会自动扩容;举一反三,有界队列就是队列容量固定不变。
-
阻塞队列:
简单来讲,阻塞队列就是指,当队列元素为空,获取元素的线程会等待队列变为非空,除非线程关闭;队列满时,添加元素的线程也会等待队列可用。同理,非阻塞队列,就是,出现上述情况时,立马返回结果
-
队列排序:
网上很多教程说是对头元素的延迟到期时间最长。这边按照实际业务情况应该是:
元素从尾部插入队列,然后按照时间排序,调用内部的CompareTo方法,最先到期的会存放在对头,每次调用take方法获取头部元素,会根据头部元素是否到期,决定接下来的逻辑。
-
不能将null元素放在这种队列中,会导致所有获取元素的线程阻塞。
适用场景
可用于所有延迟处理的业务场景,比如下单后一段时间未支付更改订单状态,定时任务调度等。
DelayQueue定时任务
DelayQueue只能添加实现了Delayed接口的对象。
-
创建对象,实现Delayed接口,重写方法
-
创建工具类,定义队列,提供添加元素方法
此处实现
-
创建对象,像队列中塞数据
源码分析
-
offer()方法
public boolean offer(E e) {
final ReentrantLock lock = this.lock;//获取锁
lock.lock();
try {
q.offer(e);//向队列中插入数据,方法内部判断插入的数据是否为null,是的话就抛出空指针异常。
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();//释放锁
}
}
简单解释一下offer()执行流程:
-
获取锁,保证线程安全;
-
向队列中插入元素,元素为null就抛异常;
-
判断当前头部元素是否就是目前新增的元素,是的话就把leader线程设置为null。leader指的是当前操作该队列的线程,如果把leader置为空,说明当前没有线程操作该队列,这很容易理解,因为元素一开始没数据,肯定没有线程操作这个队列,就算有线程获取数据,也因为没有元素,进入了线程阻塞;
-
available.signal()方法,唤醒线程,意思就是告诉其他线程,队列现在有数据啦,你们可以自由发挥啦。
-
释放锁,释放资源。
-
take()方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;//获取锁--1
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();//获取第一个元素
if (first == null)
available.await();//如果第一个元素是空,说明队列没数据,则获取线程进入等待;--2
else {
long delay = first.getDelay(NANOSECONDS);//获取当前数据的剩余到期时间--3
if (delay <= 0)
return q.poll();
first = null; // don't retain ref while waiting ---4
if (leader != null)
available.await();//如果当前已经有线程在操作队列,则该线程等待;---5
else {
Thread thisThread = Thread.currentThread();
leader = thisThread; // ---6
try {
available.awaitNanos(delay);//等待剩余到期时间,然后执行 --7
} finally {
if (leader == thisThread)
leader = null; //---8
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();// ---9
lock.unlock();// ---10
}
}
执行流程说明:
-
获取锁,保证线程安全;
-
获取队列头部第一个元素,如果队首为空,则线程阻塞,说明当前队列没有元素,空队列;这就是为什么一开始说不能插入null元素;
-
如果第一个元素不是空,则获取他的剩余到期时间,getDelay()方法就是我们重写的方法,如果到期时间小于0,说明已经到期,直接返回当前数据;
-
如果获取的第一个元素还未到期,则释放first的引用(first=null),防止内存泄露;
-
判断当前有没有其他线程正在操作该队列,如果leader不是null,说明有线程在操作,设置当前线程阻塞,available.await();
-
如果没有其他线程在操作队列,则将当前线程设置为leader;这个时候如果有其他线程访问这个队列,就会看到leader不是null,进入第5步;
-
线程阻塞,阻塞时间为剩余到期时间;
-
执行结束,将leader设置为null,让其他线程有机会变成leader;
-
如果leader是null,并且队列元素不是空,唤醒其他线程
-
浙公网安备 33010602011771号