DelayQueue
DelayQueue是一种延迟队列,它所管理的对象必须实现java.util.concurrent.Delayed接口,该接口提供了一个getDelay方法,用于获取剩余的延迟时间,同时该接口继承自Comparable,其compareTo的实现体一般用于比较延迟时间的大小。
DelayQueue是阻塞的优先级队列,其线程安全由重入锁ReentrantLock实现,而优先级特性则完全有内部PriorityQueue来提供。
java延迟队列提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。没有过期元素的话,使用poll()方法会返回null值,超时判定是通过getDelay(TimeUnit.NANOSECONDS)方法的返回值小于等于0来判断。延时队列不能存放空元素。
1,成员变量
1 //重入锁
2 private final transient ReentrantLock lock = new ReentrantLock();
3 //优先级队列,DelayQueue的数据容器
4 private final PriorityQueue<E> q = new PriorityQueue<E>();
5 //正在执行take的线程,领头线程
6 private Thread leader = null;
7 //条件监视器,用于阻塞当前线程
8 private final Condition available = lock.newCondition();
2,add/offer方法
add和offer方法都是入队方法,因为add(E)把实现直接委托给了offer。
1 public boolean offer(E e) {
2 final ReentrantLock lock = this.lock;
3 //上锁,保证代码块的线程安全
4 lock.lock();
5 try {
6 //把数据放入队列
7 q.offer(e);
8 //如果新入队的这个元素被排到堆顶
9 if(1.peek() == e) {
10 //如果有poll和take函数正在等待,那么唤醒之,并清空领头线程
11 ledader == null;
12 available.signal();
13 }
14 return true;
15 } finally {
16 lock.unlock();
17 }
18 }
offer方法的整体思路很清晰,通过利用PriorityQueue的offer方法,来达到等待时间越短的元素越靠近堆顶的目的。
但是,唤醒其他队列是什么含义呢?
当堆为空的时候,如果之前有线程执行过take(),那么线程会一直阻塞。新入队的元素e一定满足q.peek() == e,此时需要唤醒正在等待的leader线程。
当堆不为空的时候,q.peek() == e,则说明堆内没有等待时间比e更短的元素,如果之前其他线程执行过take(),那么线程同样也会阻塞,并且期望去除元素e,所以也许需要唤醒leader线程,使之重新获取堆顶元素。
为什么唤醒的一定是leader线程呢?因为leader线程一定是最先调用到的await的,所以也会第一个被唤醒,而leader==null这个判断在take方法里构成了一个自旋锁,只有满足该条件,才能重置leader为当前线程。
3,poll/peek方法
poll/peek方法用于阻塞地取出堆顶元素:
1 public E poll() {
2 final ReentrantLock lock = this.lock;
3 lock.lock();
4 try {
5 E first = q.peek();
6 //如果堆顶元素没有超时,那么返回null
7 if(first == null || first.getDelay(NANOSECONDS) > 0) {
8 return null;
9 } else {
10 return q.poll();
11 }
12 } finally {
13 lock.unlock();
14 }
15 }
16 public E peek() {
17 final ReentrantLock lock = this.lock();
18 lock.lock();
19 try {
20 return q.peek();
21 } finally {
22 lock.unlock();
23 }
24 }
4,take方法
take方法是有阻塞地获取堆顶元素,如果获取失败,那么他会不停的尝试。
1 public E take() throws InterruptedException {
2 final ReentrantLock lock = this.lock();
3 lock.lockInterruptibly();
4 try {
5 //死循环以保证获取的尝试一定成功
6 for(;;) {
7 E first = q.peek();
8 //队列为空时阻塞当前线程
9 if(first == null) {
10 available.await();
11 } else {
12 //验证是否超时,如果已经超时,那额直接返回堆顶元素
13 long delay = first.getDelay(NANOSECONDS);
14 if(delay <= 0) {
15 return q.poll();
16 }
17 first = null;
18 //如果已经存在了领头线程,那么需要等待领头线程完成操作
19 if(leader != null) {
20 available.await();
21 } else {
22 //没有领头线程存在的时候,当前线程即为领头线程,等待剩余的超时时长
23 Thread thisThread = Thread.currentThread();
24 leader = thisThread;
25 try {
26 available.awaitNanos(delay);
27 } finally {
28 //如果当前线程是领头线程,那么认为任务完成,清空领头线程
29 if(leader == thisThread) {
30 leader = null;
31 }
32 }
33 }
34 }
35 }
36 } finally {
37 //leader == null 说明有下一个线程在等待,q.peek() != null说明队列里有数据
38 //通知下一个出队的线程执行过
39 if(leader == null && q.peek() != null) {
40 available.signal();
41 }
42 lock.unlock();
43 }
44 }
5,举例
每5秒取出一个
1 public class DelayedQueneTest {
2
3 public static void main(String[] args) throws InterruptedException {
4 Item item1 = new Item("item1", 5, TimeUnit.SECONDS);
5 Item item2 = new Item("item2",10, TimeUnit.SECONDS);
6 Item item3 = new Item("item3",15, TimeUnit.SECONDS);
7 DelayQueue<Item> queue = new DelayQueue<>();
8 queue.put(item1);
9 queue.put(item2);
10 queue.put(item3);
11 System.out.println("begin time:" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
12 for (int i = 0; i < 3; i++) {
13 Item take = queue.take();
14 System.out.format("name:{%s}, time:{%s}\n",take.name, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
15 }
16 }
17
18 }
19
20 class Item implements Delayed{
21 /* 触发时间*/
22 private long time;
23 String name;
24
25 public Item(String name, long time, TimeUnit unit) {
26 this.name = name;
27 this.time = System.currentTimeMillis() + (time > 0? unit.toMillis(time): 0);
28 }
29
30 @Override
31 public long getDelay(TimeUnit unit) {
32 return time - System.currentTimeMillis();
33 }
34
35 @Override
36 public int compareTo(Delayed o) {
37 Item item = (Item) o;
38 long diff = this.time - item.time;
39 if (diff <= 0) {// 改成>=会造成问题
40 return -1;
41 }else {
42 return 1;
43 }
44 }
45
46 @Override
47 public String toString() {
48 return "Item{" +
49 "time=" + time +
50 ", name='" + name + '\'' +
51 '}';
52 }
53 }