延迟队列DelayQueue研究

1.什么是DelayQueue

     DelayQueue 是 Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即使无法使用 take 或 poll 移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size 方法同时返回到期和未到期元素的计数。此队列不允许使用 null 元素。

总结一下如下:

     1、DelayQueue队列中的元素必须是Delayed接口的实现类,该类内部实现了getDelay()和compareTo()方法,第一个方法是比较两个任务的延迟时间进行排序,第二个方法用来获取延迟时间。 
     2、DelayQueue队列没有大小限制,因此向队列插数据不会阻塞 
     3、DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。否则线程阻塞。 
     4、DelayQueue中的元素不能为null。
     5、DelayQueue内部是使用PriorityQueue实现的。compareTo()比较后越小的越先取出来。

2.使用场景

DelayQueue能做什么?

在我们的业务中通常会有一些需求是这样的:

1、订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。

2、订餐通知:下单成功后60s之后给用户发送短信通知。

那么这类业务我们可以总结出一个特点:需要延迟工作。
由此的情况,就是我们的DelayQueue应用需求的产生。

3.简单实例

下面通过一个简单实例来了解一用法

 
  1. public static void main(String[] args) {

  2.     DelayQueue<DelayedElement> delayQueue = new DelayQueue<DelayedElement>();

  3.  
  4.     //生产者

  5.     producer(delayQueue);

  6.  
  7.     //消费者

  8.     consumer(delayQueue);

  9.  
  10.     while (true) {

  11.         try {

  12.             TimeUnit.HOURS.sleep(1);

  13.         } catch (InterruptedException e) {

  14.             e.printStackTrace();

  15.         }

  16.     }

  17. }

  18.  
  19. /**

  20.  * 每100毫秒创建一个对象,放入延迟队列,延迟时间1毫秒

  21.  * @param delayQueue

  22.  */

  23. private static void producer(final DelayQueue<DelayedElement> delayQueue) {

  24.     new Thread(new Runnable() {

  25.         @Override

  26.         public void run() {

  27.             while (true) {

  28.                 try {

  29.                     TimeUnit.MILLISECONDS.sleep(100);

  30.                 } catch (InterruptedException e) {

  31.                     e.printStackTrace();

  32.                 }

  33.  
  34.                 DelayedElement element = new DelayedElement(1000, "test");

  35.                 delayQueue.offer(element);

  36.             }

  37.         }

  38.     }).start();

  39.  
  40.     /**

  41.      * 每秒打印延迟队列中的对象个数

  42.      */

  43.     new Thread(new Runnable() {

  44.         @Override

  45.         public void run() {

  46.             while (true) {

  47.                 try {

  48.                     TimeUnit.MILLISECONDS.sleep(1000);

  49.                 } catch (InterruptedException e) {

  50.                     e.printStackTrace();

  51.                 }

  52.                 System.out.println("delayQueue size:" + delayQueue.size());

  53.             }

  54.         }

  55.     }).start();

  56. }

  57.  
  58. /**

  59.  * 消费者,从延迟队列中获得数据,进行处理

  60.  * @param delayQueue

  61.  */

  62. private static void consumer(final DelayQueue<DelayedElement> delayQueue) {

  63.     new Thread(new Runnable() {

  64.         @Override

  65.         public void run() {

  66.             while (true) {

  67.                 DelayedElement element = null;

  68.                 try {

  69.                     element = delayQueue.take();

  70.                 } catch (InterruptedException e) {

  71.                     e.printStackTrace();

  72.                 }

  73.                 System.out.println("now=" + System.currentTimeMillis() + "---" + element);

  74.             }

  75.         }

  76.     }).start();

  77. }

}

 
  1. class DelayedElement implements Delayed {

  2.  
  3. private final long   delay; //延迟时间

  4. private final long   expire;  //到期时间

  5. private final String msg;   //数据

  6. private final long   create; //创建时间

  7.  
  8. public DelayedElement(long delay, String msg) {

  9.     this.delay = delay;

  10.     this.msg = msg;

  11.     expire = System.currentTimeMillis() + delay;    //到期时间 = 当前时间+延迟时间

  12.     create = System.currentTimeMillis();

  13. }

  14.  
  15. /**

  16.  * 需要实现的接口,获得延迟时间   用过期时间-当前时间

  17.  * @param unit

  18.  * @return

  19.  */

  20. @Override

  21. public long getDelay(TimeUnit unit) {

  22.     return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);

  23. }

  24.  
  25. /**

  26.  * 用于延迟队列内部比较排序   当前时间的延迟时间 - 比较对象的延迟时间

  27.  * @param o

  28.  * @return

  29.  */

  30. @Override

  31. public int compareTo(Delayed o) {

  32.     return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));

  33. }

  34.  
  35. @Override

  36. public String toString() {

  37.     final StringBuilder sb = new StringBuilder("DelayedElement{");

  38.     sb.append("delay=").append(delay);

  39.     sb.append(", expire=").append(expire);

  40.     sb.append(", msg='").append(msg).append('\'');

  41.     sb.append(", create=").append(create);

  42.     sb.append('}');

  43.     return sb.toString();

  44. }

运行结果:

 

4.源码分析  

下面来看看其主要实现的源码分析

(1)、从队列中取元素

 
  1. public E take() throws InterruptedException {  

  2.     final ReentrantLock lock = this.lock;  

  3.     lock.lockInterruptibly();  

  4.     try {  

  5.         for (;;) {  

  6.             E first = q.peek();  

  7.             if (first == null)  

  8.                 available.await();  

  9.             else {  

  10.                 long delay = first.getDelay(TimeUnit.NANOSECONDS);  

  11.                 if (delay <= 0)  

  12.                     return q.poll();  

  13.                 else if (leader != null)  

  14.                     available.await();  

  15.                 else {  

  16.                     Thread thisThread = Thread.currentThread();  

  17.                     leader = thisThread;  

  18.                     try {  

  19.                         available.awaitNanos(delay);  

  20.                     } finally {  

  21.                         if (leader == thisThread)  

  22.                             leader = null;  

  23.                     }  

  24.                 }  

  25.             }  

  26.         }  

  27.     } finally {  

  28.         if (leader == null && q.peek() != null)  

  29.             available.signal();  

  30.         lock.unlock();  

  31.     }  

可以看到,在这段代码里,在第一个元素的延迟时间还没到的情况下: 
a.如果当前没有其他线程等待,则阻塞当前线程直到延迟时间。 
b.如果有其他线程在等待,则阻塞当前线程。 

(2)、向队列中放入元素

 
  1. public boolean offer(E e) {

  2.     final ReentrantLock lock = this.lock;

  3.     lock.lock();

  4.     try {

  5.         q.offer(e);

  6.         if (q.peek() == e) {

  7.             leader = null;

  8.             available.signal();

  9.         }

  10.         return true;

  11.     } finally {

  12.         lock.unlock();

  13.     }

  14. }

在放入元素的时候,会唤醒等待中的读线程。

 

5.进阶-实现一个延迟缓存队列

实现一个带延迟缓存队列

(1)、定义delayIetm对象

 

 
  1. public class DelayItem<T> implements Delayed  {

  2.  
  3. //创建时间

  4. private long MILLISECONDS_ORIGIN = System.currentTimeMillis();

  5.  
  6. //元素

  7. private T item;

  8.  
  9. //元素的存活时间,单位为毫秒 (unit:milliseconds)

  10. private long liveMilliseconds;

  11.  
  12. public DelayItem(T item, long liveMilliseconds) {

  13.     this.liveMilliseconds = liveMilliseconds;

  14.     this.item = item;

  15. }

  16.  
  17. private final long now() {

  18.     return System.currentTimeMillis() - MILLISECONDS_ORIGIN;

  19. }

  20.  
  21. public void setMilliseconds(long milliseconds) {

  22.     MILLISECONDS_ORIGIN = System.currentTimeMillis();

  23.     this.liveMilliseconds = milliseconds;

  24. }

  25.  
  26. /**

  27.  * 如果超时,或者Map缓存中已经没有该元素,都会导致失效

  28.  *

  29.  * @param unit

  30.  * @return

  31.  */

  32. public long getDelay(TimeUnit unit) {

  33.     long d = unit.convert(liveMilliseconds - now(), TimeUnit.MILLISECONDS);

  34.  
  35.     //        LOGGER.debug("=============key:" + item + ",time:" + milliseconds + " , now:" + now() + ",times:{}", checkTimesLeft);

  36.     return d;

  37. }

  38.  
  39.  
  40. @Override

  41. public boolean equals(Object obj) {

  42.     if (obj instanceof com.github.lin.DelayItem) {

  43.         return item.equals(((com.github.lin.DelayItem) obj).getItem());

  44.     } else {

  45.         return false;

  46.     }

  47. }

  48.  
  49. public int compareTo(Delayed o) {

  50.     if (o == this) {

  51.         return 0;

  52.     }

  53.  
  54.     //根据距离下次超时时间的长短来排优先级,越接近下次超时时间的优先级越高

  55.     long d = (getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS));

  56.     return (d == 0) ? 0 : ((d < 0) ? -1 : 1);

  57. }

  58.  
  59. public T getItem() {

  60.     return item;

  61. }

  62. }

 

(2)、定义缓存工厂

 
  1. public class CacheFactory<K, V> {

  2.  
  3. /**缓存map*/

  4. private ConcurrentHashMap<K, V> concurrentHashMap = new ConcurrentHashMap<K, V>();

  5.  
  6. /**延迟队列*/

  7. private DelayQueue<com.github.lin.DelayItem<K>> delayQueue = new DelayQueue<com.github.lin.DelayItem<K>>();

  8.  
  9. /**过期检查队列*/

  10. private Thread expireCheckThread;

  11.  
  12. public CacheFactory() {

  13.     //定时清理过期缓存

  14.     expireCheckThread = new Thread() {

  15.         @Override

  16.         public void run() {

  17.             dameonCheckOverdueKey();

  18.         }

  19.     };

  20.     expireCheckThread.setDaemon(true);

  21.     expireCheckThread.start();

  22. }

  23.  
  24. /**

  25.  * 放入带有过期时间的元素

  26.  * @param key

  27.  * @param value

  28.  * @param liveMilliseconds

  29.  */

  30. public void put(K key, V value, long liveMilliseconds) {

  31.     V oldValue = concurrentHashMap.put(key, value);

  32.     if (oldValue != null) {

  33.         //todo 这个地方性能比较差,DelayQueue删除元素慢

  34.         boolean result = delayQueue.remove(new com.github.lin.DelayItem<K>(key, 0L));

  35.     }

  36.  
  37.     com.github.lin.DelayItem delayItem = new com.github.lin.DelayItem(key, liveMilliseconds);

  38.     delayQueue.put(delayItem);

  39. }

  40.  
  41. /**

  42.  * 取元素

  43.  * @param key

  44.  * @return

  45.  */

  46. public V get(K key) {

  47.     return concurrentHashMap.get(key);

  48. }

  49.  
  50. /**

  51.  * 检查过期的key,从cache中删除

  52.  */

  53. private void dameonCheckOverdueKey() {

  54.     DelayItem<K> delayedItem;

  55.     while (true) {

  56.         try {

  57.             delayedItem = delayQueue.take();

  58.             if (delayedItem != null) {

  59.                 concurrentHashMap.remove(delayedItem.getItem());

  60.                 System.out.println(System.nanoTime() + " remove " + delayedItem.getItem() + " from cache");

  61.             }

  62.         } catch (InterruptedException e) {

  63.             e.printStackTrace();

  64.         }

  65.     }

  66. }

  67. }

其实就是一个map和一个queue来实现,后台有一个守护线程,如果检查到有过期的key。就从queue里取出来,再从map删除。

(3)、测试用例

 

 
  1. public class CacheFactoryTest {

  2.  
  3. public static void main(String[] args) throws InterruptedException {

  4.     CacheFactory<String,String> cacheFactory = new CacheFactory<String, String>();

  5.     //存活1s

  6.     cacheFactory.put("key1","value1",1000);

  7.  
  8.     //存活10s

  9.     cacheFactory.put("key2","value2",10000);

  10.  
  11.     System.out.println("begin get key1:" + cacheFactory.get("key1"));

  12.     System.out.println("begin get key2:" +cacheFactory.get("key2"));

  13.  
  14.     //等待2s

  15.     Thread.sleep(2000);

  16.  
  17.     System.out.println("after 2s:" +cacheFactory.get("key1"));

  18.     System.out.println("after 2s:" +cacheFactory.get("key2"));

  19.  
  20.     //等待10s

  21.     Thread.sleep(10000);

  22.  
  23.     System.out.println("after 10s:" +cacheFactory.get("key1"));

  24.     System.out.println("after 10s:" +cacheFactory.get("key2"));

  25.  
  26. }

  27. }

执行结果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值