DelayQueue
介绍
继承关系如下:
DelayQueue是一个支持获取元素的无界阻塞队列。队列使用PriorityQueue来实现,关于PriorityQueue的方法见另一篇文章
小白学PriorityQueue ,队列中的元素必须实现Delayed接口,实现getDelay方法,该方法返回指定的延迟时间,即该元素存活多久后可以从队列中提取元素
举个例子:
// 参考最下的链接
public class DelayQueueTest1 {
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("task4",4000));
delayQueue.offer(new MyDelayedTask("task2",2000));
delayQueue.offer(new MyDelayedTask("task1",1000));
delayQueue.offer(new MyDelayedTask("task3",3000));
}
}).start();
// 循环去队列取元素,只有当队列中的元素延迟时间结束后才能取出元素,否则将阻塞
while (true) {
Delayed take = delayQueue.take();
System.out.println(take);
}
}
}
/**
* compareTo 方法必须提供与 getDelay 方法一致的排序
*/
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));
}
@Override
public String toString() {
return "MyDelayedTask{" +
"name='" + name + '\'' +
", time=" + time +
'}';
}
}
调试结果如下:
可以看出延迟时间最小的元素在第一个位置,满足小顶堆
的排序规则
延迟队列在日常工作中也十分常见,比如:
- 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,如果从DelayQueue获取到元素,说明该元素已经到期了
- 定时任务调度:使用DelayQueue保存当天执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现。
下面对核心代码进行解读
基本属性
private final transient ReentrantLock lock = new ReentrantLock();
// 优先级队列, 使用堆排序,插入的每个元素放在这个队列中
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 当某个线程成为leader后, 其他线程将无限等待,只有在当前线程延迟时间结束后,才将leader释放赋null
// 在take、poll(timeout, unit) 返回元素之前必须唤醒其他线程
private Thread leader = null;
// 线程没有获取到元素,那么就将线程放入该等待队列中
private final Condition available = lock.newCondition();
// 构造
public DelayQueue() {}
插入元素(offer)
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();
}
}
获取元素(take)
当延迟时间最短的元素时间等待时间结束后才返回, 如果一直没有获取到元素,线程会一直阻塞,直到被中断
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)
// 弹出元素,先要执行finally中代码
return q.poll();
first = null; // don't retain ref while waiting
// 在当前线程进入之前已经有其他线程更改了leader
// 因为await方法会释放所有锁资源,因此会有其他线程进入该方法
if (leader != null)
available.await();
else {
// 线程首次调用take时会执行该处
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 阻塞当前元素的延迟时间
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
// 赋null,让其他线程获取leader
leader = null;
}
}
}
}
} finally {
// 没有线程持有leader 并且有其他线程在等待队列中, 那么唤醒等待队列中的其他等待线程
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
弹出元素(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();
}
}
指定时间弹出元素
指定等待时间,如果在指定时间内还没有获取到一个过期的元素,那么直接返回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();
// PriorityQueue中没有元素
if (first == null) {
// 等待时间已经到,返回null
if (nanos <= 0)
return null;
else
// 等待指定时间
nanos = available.awaitNanos(nanos);
} else { // 当first不为null时
// 获取first所需的时间
long delay = first.getDelay(NANOSECONDS);
// 时间到了,直接返回
if (delay <= 0)
return q.poll();
if (nanos <= 0)
return null;
first = null; // don't retain ref while waiting
// 先等待naos 时间
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
// 第一次需要设置当前线程为leader
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
long timeLeft = available.awaitNanos(delay);
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
参考链接:
https://www.cnblogs.com/myseries/p/10944211.html