1 DelayQueue的应用
DelayQueue是无界队列,队列中的元素要实现Delayed接口,重写getDelay和compareTo方法
-
getDelay:什么时候可以出队列
-
compareTo:存放到队列时,放在二叉堆的哪个位置
用一个类来模拟延迟队列中的元素
class Task implements Delayed{
private String name;
/** 执行时间 (单位毫秒) */
private Long time;
/**
*
* @param name 任务名称
* @param delayTime 传入延迟时间
*/
public Task(String name, Long delayTime) {
this.name = name;
this.time = System.currentTimeMillis() + delayTime;
}
/** 任务可以出队列的核心方法 */
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(time - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
/** 通过这个方法,来比较,将任务存放到二叉堆的指定位置 */
@Override
public int compareTo(Delayed o) {
// 基于执行时间比较
return (int) (this.time - ((Task)o).getTime());
}
}
- 延迟时间到了,才可以将元素取出来。
public static void main(String[] args) throws InterruptedException {
DelayQueue queue = new DelayQueue();
queue.offer(new Task("A",4000L));
queue.offer(new Task("B",2000L));
queue.offer(new Task("C",3000L));
queue.offer(new Task("D",1000L));
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
}
// 输出结果
Task(name=D, time=1684310000097)
Task(name=B, time=1684310001097)
Task(name=C, time=1684310002097)
Task(name=A, time=1684310003097)
2 DelayQueue源码分析
DelayQueue是基于PriorityQueue实现的,想了解DelayQueue的源码,需要先了解PriorityQueue
可以参考Java并发编程(十三)PriorityBlockingQueue
2.1 DelayQueue的核心属性
// 二叉堆,实际元素的存放位置
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 锁
private final transient ReentrantLock lock = new ReentrantLock();
// 配合锁使用,阻塞唤醒线程
private final Condition available = lock.newCondition();
// Leader-Follower线程模型
private Thread leader;
2.2 存数据
- 所有存数据的方法最后调用的都是offer方法
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 调用优先级队列,添加任务
q.offer(e);
// 拿到第一个数据,看看我是不是第一个,如果是第一个,可能有消费者挂起了,唤醒消费者
if (q.peek() == e) {
// 将leader更新为null,并唤醒等待线程
leader = null;
// condition.signal()唤醒线程
available.signal();
}
// ok~返回true
return true;
} finally {
lock.unlock();
}
}
// 下面两个方法都是直接调用offer方法
public void put(E e) {
offer(e);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
2.3 取数据
- poll,消费者尝试一下拿数据,如果有数据,并且延迟时间已经到了,返回,否则返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 拿到堆顶数据
E first = q.peek();
// 如果没数据,或者数据的延迟时间没到,返回null
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
// 如果有数据,并且时间到了,基于优先级队列,把任务取出来。
return q.poll();
} finally {
lock.unlock();
}
}
- poll(long timeout, TimeUnit unit),消费者尝试获取并等待至有数据可以取出或超时返回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);
} else {
// 必然有数据!!!!
// 取出堆顶数据的剩余时间
long delay = first.getDelay(NANOSECONDS);
// 如果时间已经到位了,直接调用优先级队列,把数据取出来
if (delay <= 0)
return q.poll();
// 再次判断等待时间
if (nanos <= 0)
// 不等,告辞
return null;
// 将临时变量置位null
first = null;
// 如果剩余的等待时间,小于任务的延迟时间,或者已经有leader线程在等待了
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
// 我等待的时间内,必然可以拿到数据,并且没有leader
Thread thisThread = Thread.currentThread();
// 将当前线程置位leader,说明我是第一个在这等待数据的线程
leader = thisThread;
try {
// 当前线程先挂起,挂起任务剩余的延迟时间,会释放锁!
long timeLeft = available.awaitNanos(delay);
// 重新计算剩余的等待时间
nanos -= delay - timeLeft;
} finally {
// 将leader置位null
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 如果leader为null,并且堆顶有数据,执行唤醒操作
if (leader == null && q.peek() != null)
available.signal();
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 <= 0L)
// 时间已经到了,直接取出返回
return q.poll();
// don't retain ref while waiting
// 等待时不持有引用
first = null;
if (leader != null)
// 已经有leader在等了,作为follower在这里等待
available.await();
else {
// 没有leader在等,当前线程为leader
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// leader只需要等待到延迟时间到了就行,然后执行finally中的代码,进入下一次循环
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}