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 }
posted @ 2020-08-13 10:31  光何  阅读(148)  评论(0编辑  收藏  举报