学习目标
DelayQueue是什么?DelayQueue是一个支持延时获取元素的无界阻塞队列。里面的元素全部都是“可延期”的元素,列头的元素是最先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期到时才能够从队列中取元素。
那么我们可以从哪些场景应用延迟队列呢?
- 比如延迟会话关闭,当我们发现用户再下单一段时间还未支付时可以触发延时队列去发送消息或者自动取消订单
- 比如饿了吗订餐通知:下单成功后60s之后给用户发送短信通知
- 比如缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出
- 任务超时处理
那么我们可以来看看延迟队列DelayQueue
到底是如何使用和如何实现的。
实例运用
static class DelayedItem<T> implements Delayed {
T value;
long time = 0;
public DelayedItem(T v, long delay){
this.value = v;
this.time = delay + System.currentTimeMillis();
}
@Override
public long getDelay(TimeUnit unit) {
return time - System.currentTimeMillis();
}
@Override
public int compareTo(Delayed o) {
return (int) (this.time - ((DelayedItem)o).time);
}
@Override
public String toString() {
return "DelayedInteger{" +
"value=" + value +
", time=" + time+
'}';
}
}
static DelayQueue<DelayedItem<Integer>> queue = new DelayQueue<>();
public static void main(String[] argv) {
new Thread(() -> {
for(int i = 0; i < 1000; i++){
queue.offer(new DelayedItem<Integer>(i, i*1000));
}
}).start();
new Thread(() -> {
while(true) {
try {
DelayedItem<Integer> item = queue.take();
System.out.println(item.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
运行情况:发现每隔一秒执行take后的代码
DelayedInteger{value=0, time=1637746185984}
DelayedInteger{value=1, time=1637746186984}
DelayedInteger{value=2, time=1637746187984}
DelayedInteger{value=3, time=1637746188984}
DelayedInteger{value=4, time=1637746189984}
DelayedInteger{value=5, time=1637746190985}
DelayedInteger{value=6, time=1637746191985}
DelayedInteger{value=7, time=1637746192985}
DelayedInteger{value=8, time=1637746193985}
DelayedInteger{value=9, time=1637746194985}
DelayedInteger{value=10, time=1637746195985}
从上面例子我们可以看到在定义DelayQueue时中的类型必须要实现Delayed接口。
DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象,意思是说我们不能想往DelayQueue里添加什么就添加什么,不能添加int、也不能添加String进去,必须添加我们自己的实现了Delayed接口的类的对象
static class DelayedItem<T> implements Delayed
其中,compareTo 方法 getDelay 方法 就是Delayed接口的方法,我们必须实现,而且按照JAVASE文档,compareTo 方法必须提供与 getDelay 方法一致的排序,用于执行先后顺序,也就是说compareTo方法里可以按照getDelay方法的返回值大小排序,即在compareTo方法里比较getDelay方法返回值大小
元素进入队列后,先进行排序,然后,只有getDelay也就是剩余时间为0的时候,该元素才有资格被消费者从队列中取出来,所以构造函数一般都有一个时间传入
DelayQueue结构
可以通过观察DelayQueue内部代码:
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 signalled when a newer element becomes available
* at the head of the queue or a new thread may need to
* become leader.
*/
private final Condition available = lock.newCondition();
/**
* Creates a new {@code DelayQueue} that is initially empty.
*/
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)
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();
}
}
可以看到当获取当前时间小于等于0时才返回元素,否则继续等待[available.awaitNanos(delay)]
。
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();