什么是DelayQueue?
根据官方文档描述,DelayQueue是一种存储含过期时间的元素的无界(队列无容量限制)阻塞(使用到了锁)队列;队列头部元素是已经过期并且是过期时间最久的元素(在peek方法实现中,队头元素不一定符合这一定义,队头元素也有可能是未过期,但是队列中离过期时间最近的元素),如果队列中没有元素过期,则队列头部没有元素(对于poll方法而言),使用poll方法时会返回null;除此之外,这些没有过期的元素会当作正常元素对待,使用size获取队列长度时,会返回所有过期、未过期的元素之和。
DelayQueue中存储的元素有何要求?
(1)所有想要进入DelayQueue中的元素都必须实现一个接口:Delayed。
(2)Delay接口又继承自Comparable接口,这就意味着元素在实现getDelay方法的同时还要实现compareTo方法;getDelay方法的实现逻辑一般是返回元素剩余的过期时间(负数x代表已经过期x时间、0代表当前正好过期、正数y代表再过y时间后过期)
(3)compareTo方法的实现逻辑一般是比较两个元素getDelay方法的返回值,根据比较结果,由优先级队列组织元素在队列中的先后顺序。
DelayQueue字段分析
(1)ReentrantLock:用于线程同步,锁的一种实现(支持可重入)。
(2)PriorityQueue:优先级队列,DelayQueue底层使用该队列基于元素的剩余过期时间进行排序,规定元素的弹出次序。
(3)leader:leader线程,应用leader/follower模式的思想。
(4)Condition:线程等待时的“休息室”,与ReentrantLock配套使用。
//lock实现
private final transient ReentrantLock lock = new ReentrantLock();
//基于优先级队列控制元素的弹出次序
private final PriorityQueue<E> q = new PriorityQueue<E>();
//leader线程
private Thread leader = null;
//线程await时的“休息室”
private final Condition available = lock.newCondition();
DelayQueue关键方法分析
(1)offer:添加元素到队列中(add、put底层都是直接调用该方法)。
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
//首先获取锁
lock.lock();
try {
//获取锁成功后将元素加入优先级队列中
q.offer(e);
//如果此时队列头部元素就是刚加入队列中的元素
//1.说明此时队列中全是未过期的元素
//2.可能有线程正在等待队头元素过期,将leader线程置为null
//3.//唤醒在休息室等待的线程
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
//在finally块释放锁,防止死锁
lock.unlock();
}
}
(2)peek:取出队列头部元素。
public E peek() {
final ReentrantLock lock = this.lock;
//获取锁
lock.lock();
try {
//队列可能存在三种情况:
//1.所有元素均未到过期时间:此时返回离过期时间最近的元素
//2.有一部分元素过期、一部分未过期:此时返回过期元素中,过期最久的元素
//3.所以元素都过期了:此时返回已经过期的元素中,过期最久的元素
return q.peek();
} finally {
//finally块释放锁,防止死锁
lock.unlock();
}
}
(3)poll:取出并移除队列头部元素。
public E poll() {
final ReentrantLock lock = this.lock;
//获取锁
lock.lock();
try {
E first = q.peek();
//1.队列中没有元素:first == null
//2.队列中所有元素都还没过期:first.getDelay(NANOSECONDS) > 0
//若上述两个现象有一个存在,直接返回null
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
//若队列既不为空,也有元素以及过期,则直接返回队列头部元素
return q.poll();
} finally {
//释放锁,防止死锁
lock.unlock();
}
}
(4)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)
//如果leader线程不为null
//说明此时已经有线程正在等待队头元素到期
available.await();
else {
//如果leader线程为空,说明此时没有线程等待队头元素到期
//1.设置当前线程为leader线程
//2.让当前线程到休息室中等待delay时间
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
//等待结束后,若当前线程还是leader线程,则将leader置为null
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//leader为null并且队列不为null,那么唤醒正在休息室等待的线程
if (leader == null && q.peek() != null)
available.signal();
//释放锁
lock.unlock();
}
}
DelayQueue的简单使用
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayQueueTest {
static DelayQueue<Message<String>> queue = new DelayQueue<Message<String>>();
public static void main(String[] args) {
//添加三个过期时间不同的元素到队列中
for (int i = 1; i <= 3; i++) {
queue.add(new Message<String>(i,"任务" + i));
}
//创建一个线程,不断取出DelayQueue中的元素,直至队列为空
new Thread(() ->{
while(!queue.isEmpty()){
Message<String> msg = queue.poll();
System.out.println(msg);
}
}).start();
}
static class Message<E> implements Delayed {
//延迟时间
long delayTime;
//过期时间:当前时间 + 延迟时间
long expireTime;
//真正的数据
E data;
public Message(long delayTime, E data) {
this.delayTime = delayTime;
this.data = data;
this.expireTime = System.currentTimeMillis() + delayTime;
}
@Override
public long getDelay(TimeUnit unit) {
long leftTime = unit.convert(this.expireTime - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
return leftTime;
}
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return "Message{" +
"delayTime=" + delayTime +
", expireTime=" + expireTime +
", data=" + data +
'}';
}
}
}
运行结果:
三个任务可以认为是同时创建,由于任务1的延迟时间最短,它最先过期,所以最先被打印的是任务1,任务2、3的打印顺序也是相同的原理。