并发编程学习笔记(二十二、DelayQueue源码分析)

目录:

  • 什么是DelayQueue
  • 如何使用DelayQueue
  • 总结

什么是DelayQueue

DelayQueue是一个支持延时获取元素无界阻塞队列,队列使用PriorityQueue来实现

队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。

DelayQueue非常有用,可以运用在以下两个应用场景:

  • 缓存系统的设计:使用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,就表示有缓存到期了
  • 定时任务调度:使用DelayQueue保存当天要执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如Timer就是使用DelayQueue实现的。

如何使用DelayQueue

既然上面说到了DelayQueue可以用于缓存系统的设计,那么我这里的demo就来模拟一下上面的场景。

使用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,就表示有缓存到期了

1、首先有个CacheData,实现Delayed接口,用作DelayQueue的数据源。

 1 /**
 2  * 缓存数据源
 3  *
 4  * @author zhoude
 5  * @date 2020/6/29 21:09
 6  */
 7 public class CacheData implements Delayed {
 8 
 9     /**
10      * 缓存过期时间,<= 0表示已过期
11      */
12     private long remaining;
13 
14     /**
15      * 缓存数据的key
16      */
17     private String key;
18 
19     /**
20      * 缓存数据的值
21      */
22     private String value;
23 
24     public CacheData(long remaining, String key, String value) {
25         this.remaining = System.currentTimeMillis() + remaining;
26         this.key = key;
27         this.value = value;
28     }
29 
30     @Override
31     public String toString() {
32         return "CacheData{" +
33                 "remaining=" + remaining +
34                 ", key='" + key + '\'' +
35                 ", value='" + value + '\'' +
36                 '}';
37     }
38 
39     @Override
40     public long getDelay(TimeUnit unit) {
41         return remaining - System.currentTimeMillis();
42     }
43 
44     @Override
45     public int compareTo(Delayed o) {
46         CacheData data = (CacheData) o;
47         long diff = this.remaining - data.remaining;
48         if (diff > 0) {
49             return 1;
50         }
51         return -1;
52     }
53 
54 //  getter and setter
55 
56     public String getKey() {
57         return key;
58     }
59 
60     public String getValue() {
61         return value;
62     }
63 
64 }

2、然后提供一个缓存的操作器。

 1 /**
 2  * 操作缓存的template
 3  *
 4  * @author zhoude
 5  * @date 2020/6/29 21:21
 6  */
 7 public class CacheTemplate extends Thread {
 8 
 9     /**
10      * 原始数据(模拟DB)
11      */
12     private Map<String, CacheData> data;
13 
14     /**
15      * 缓存数据
16      */
17     private DelayQueue<CacheData> delayQueue;
18 
19     public CacheTemplate(List<CacheData> source) {
20         data = new HashMap<>(source.size());
21         source.forEach(item -> data.put(item.getKey(), item));
22         delayQueue = new DelayQueue<>(source);
23     }
24 
25     @Override
26     public void run() {
27         for (; ; ) {
28             try {
29                 // 一直从delayQueue拿数据,拿到则说明有缓存过期了
30                 CacheData queue = delayQueue.take();
31                 String key = queue.getKey();
32                 String value = queue.getValue();
33                 System.out.println(MessageFormat.format("缓存{0}已到期,value={1}", key, value));
34             }
35             catch (InterruptedException e) {
36                 e.printStackTrace();
37             }
38         }
39     }
40 
41     public void put(String key, CacheData value) {
42         data.put(key, value);
43     }
44 
45     /**
46      * 重新设置值到delayQueue中,用于模拟缓存过期后重新获取
47      *
48      * @param key 缓存key
49      */
50     public void reset(String key) {
51         CacheData queue = data.get(key);
52         delayQueue.add(queue);
53     }
54 
55 }

3、然后我们来测试下。

 1 public class CacheTest {
 2 
 3     public static CacheTemplate template;
 4 
 5     public static void main(String[] args) throws InterruptedException {
 6         template = new CacheTemplate(getTemplateInitData());
 7         template.start();
 8         // 休眠7秒,用于模拟程序开始后用户的行为,改变DB中的数据
 9         TimeUnit.SECONDS.sleep(7L);
10         template.put("cache2", new CacheData(10000L, "cache2", "value2-modify"));
11         template.reset("cache2");
12     }
13 
14     private static List<CacheData> getTemplateInitData() {
15         List<CacheData> result = new ArrayList<>();
16         result.add(new CacheData(5000L, "cache1", "value1"));
17         result.add(new CacheData(10000L, "cache2", "value2"));
18         result.add(new CacheData(15000L, "cache3", "value3"));
19         return result;
20     }
21 
22 }

从结果我们可以看出来,cache2第二次的值会变成value2-modify,也就是从DB中获取的。

从demo看来,我们可以利用DelayQueue中take()方法的队列无值会一直阻塞获取的特性来达到缓存过期的目的

总结(源码和ArrayBlockingQueue类似)

DelayQueue是阻塞队列中非常有用的一种队列,经常被用于缓存或定时任务等的设计。

以下类似的场景也可以使用:

1、异步通知的重试

  • 在很多系统中,当用户完成服务调用后, 系统有时需要将结果异步通知到用户的某个UR。由于网络等原因,很多时候会通知失败,这个时候就需要一种重试机制。

  • 这时可以用DelayQueue保存通知失败的请求,失效时间可以根据已通知的次数来设定(比如: 2s、 5s、10s、 20s) ,这样每次从队列中take获取的就是剩余时间最短的请求,如果已重复通知次数超过一定阈值,则可以把消息抛弃。

  • 像JUC线程池框架中的Schedu JedThreadPoolExecu tor.DelayedWorkQueu e就是一种延时阻塞队列。
posted @ 2020-06-29 22:23  被猪附身的人  阅读(179)  评论(0编辑  收藏  举报