Java 延迟队列使用

延时队列,第一他是个队列,所以具有对列功能第二就是延时,这就是延时对列,功能也就是将任务放在该延时对列中,只有到了延时时刻才能从该延时对列中获取任务否则获取不到……

应用场景比较多,比如延时1分钟发短信,延时1分钟再次执行等,下面先看看延时队列demo之后再看延时队列在项目中的使用:

简单的延时队列要有三部分:第一实现了Delayed接口的消息体、第二消费消息的消费者、第三存放消息的延时队列,那下面就来看看延时队列demo。

一、消息体

 

[java] view plain copy
 
  1. package com.delqueue;  
  2.   
  3. import java.util.concurrent.Delayed;  
  4. import java.util.concurrent.TimeUnit;  
  5.   
  6. /** 
  7.  * 消息体定义 实现Delayed接口就是实现两个方法即compareTo 和 getDelay最重要的就是getDelay方法,这个方法用来判断是否到期…… 
  8.  *  
  9.  * @author whd 
  10.  * @date 2017年9月24日 下午8:57:14 
  11.  */  
  12. public class Message implements Delayed {  
  13.     private int id;  
  14.     private String body; // 消息内容  
  15.     private long excuteTime;// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。  
  16.   
  17.     public int getId() {  
  18.         return id;  
  19.     }  
  20.   
  21.     public String getBody() {  
  22.         return body;  
  23.     }  
  24.   
  25.     public long getExcuteTime() {  
  26.         return excuteTime;  
  27.     }  
  28.   
  29.     public Message(int id, String body, long delayTime) {  
  30.         this.id = id;  
  31.         this.body = body;  
  32.         this.excuteTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime();  
  33.     }  
  34.   
  35.     // 自定义实现比较方法返回 1 0 -1三个参数  
  36.     @Override  
  37.     public int compareTo(Delayed delayed) {  
  38.         Message msg = (Message) delayed;  
  39.         return Integer.valueOf(this.id) > Integer.valueOf(msg.id) ? 1  
  40.                 : (Integer.valueOf(this.id) < Integer.valueOf(msg.id) ? -1 : 0);  
  41.     }  
  42.   
  43.     // 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期否则还没到期  
  44.     @Override  
  45.     public long getDelay(TimeUnit unit) {  
  46.         return unit.convert(this.excuteTime - System.nanoTime(), TimeUnit.NANOSECONDS);  
  47.     }  
  48. }  


二、消息消费者

 

[java] view plain copy
 
  1. package com.delqueue;  
  2.   
  3. import java.util.concurrent.DelayQueue;  
  4.   
  5. public class Consumer implements Runnable {  
  6.     // 延时队列 ,消费者从其中获取消息进行消费  
  7.     private DelayQueue<Message> queue;  
  8.   
  9.     public Consumer(DelayQueue<Message> queue) {  
  10.         this.queue = queue;  
  11.     }  
  12.   
  13.     @Override  
  14.     public void run() {  
  15.         while (true) {  
  16.             try {  
  17.                 Message take = queue.take();  
  18.                 System.out.println("消费消息id:" + take.getId() + " 消息体:" + take.getBody());  
  19.             } catch (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     }  
  24. }  


三、延时队列

 

[java] view plain copy
 
  1. package com.delqueue;  
  2.   
  3. import java.util.concurrent.DelayQueue;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6.   
  7. public class DelayQueueTest {  
  8.      public static void main(String[] args) {    
  9.             // 创建延时队列    
  10.             DelayQueue<Message> queue = new DelayQueue<Message>();    
  11.             // 添加延时消息,m1 延时3s    
  12.             Message m1 = new Message(1, "world", 3000);    
  13.             // 添加延时消息,m2 延时10s    
  14.             Message m2 = new Message(2, "hello", 10000);    
  15.             //将延时消息放到延时队列中  
  16.             queue.offer(m2);    
  17.             queue.offer(m1);    
  18.             // 启动消费线程 消费添加到延时队列中的消息,前提是任务到了延期时间   
  19.             ExecutorService exec = Executors.newFixedThreadPool(1);  
  20.             exec.execute(new Consumer(queue));  
  21.             exec.shutdown();  
  22.         }    
  23. }  


将消息体放入延迟队列中,在启动消费者线程去消费延迟队列中的消息,如果延迟队列中的消息到了延迟时间则可以从中取出消息否则无法取出消息也就无法消费。

这就是延迟队列demo,下面我们来说说在真实环境下的使用。

使用场景描述:

在打车软件中对订单进行派单的流程,当有订单的时候给该订单筛选司机,然后给当订单绑定司机,但是有时运气没那么好,订单进来后第一次没有筛选到合适的司机,但我们也不能就此结束派单,而是将该订单的信息放到延时队列中过个2秒钟在进行一次,其实这个2秒钟就是一个延迟,所以这里我们就可以使用延时队列来实现……

下面看看简单的流程图:

 

下面来看看具体代码实现:

在项目中有如下几个类:第一 、任务类   第二、按照任务类组装的消息体类  第三、延迟队列管理类

任务类即执行筛选司机、绑单、push消息的任务类

 

[java] view plain copy
 
  1. package com.test.delayqueue;  
  2. /** 
  3.  * 具体执行相关业务的业务类 
  4.  * @author whd 
  5.  * @date 2017年9月25日 上午12:49:32 
  6.  */  
  7. public class DelayOrderWorker  implements Runnable {  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.         // TODO Auto-generated method stub  
  12.         //相关业务逻辑处理  
  13.         System.out.println(Thread.currentThread().getName()+" do something ……");  
  14.     }  
  15. }  

 

消息体类,在延时队列中这个实现了Delayed接口的消息类是比不可少的,实现接口时有一个getDelay(TimeUnit unit)方法,这个方法就是判断是否到期的

这里定义的是一个泛型类,所以可以将我们上面的任务类作为其中的task,这样就将任务类分装成了一个消息体

 

[java] view plain copy
 
  1. package com.test.delayqueue;  
  2.   
  3. import java.util.concurrent.Delayed;  
  4. import java.util.concurrent.TimeUnit;  
  5.   
  6. /** 
  7.  * 延时队列中的消息体将任务封装为消息体 
  8.  *  
  9.  * @author whd 
  10.  * @date 2017年9月25日 上午12:48:30 
  11.  * @param <T> 
  12.  */  
  13. public class DelayOrderTask<T extends Runnable> implements Delayed {  
  14.     private final long time;  
  15.     private final T task; // 任务类,也就是之前定义的任务类  
  16.   
  17.     /** 
  18.      * @param timeout 
  19.      *            超时时间(秒) 
  20.      * @param task 
  21.      *            任务 
  22.      */  
  23.     public DelayOrderTask(long timeout, T task) {  
  24.         this.time = System.nanoTime() + timeout;  
  25.         this.task = task;  
  26.     }  
  27.   
  28.     @Override  
  29.     public int compareTo(Delayed o) {  
  30.         // TODO Auto-generated method stub  
  31.         DelayOrderTask other = (DelayOrderTask) o;  
  32.         long diff = time - other.time;  
  33.         if (diff > 0) {  
  34.             return 1;  
  35.         } else if (diff < 0) {  
  36.             return -1;  
  37.         } else {  
  38.             return 0;  
  39.         }  
  40.     }  
  41.   
  42.     @Override  
  43.     public long getDelay(TimeUnit unit) {  
  44.         // TODO Auto-generated method stub  
  45.         return unit.convert(this.time - System.nanoTime(), TimeUnit.NANOSECONDS);  
  46.     }  
  47.   
  48.     @Override  
  49.     public int hashCode() {  
  50.         return task.hashCode();  
  51.     }  
  52.   
  53.     public T getTask() {  
  54.         return task;  
  55.     }  
  56. }  

 

延时队列管理类,这个类主要就是将任务类封装成消息并并添加到延时队列中,以及轮询延时队列从中取出到时的消息体,在获取任务类放到线程池中执行任务

 

[java] view plain copy
 
  1. package com.test.delayqueue;  
  2.   
  3. import java.util.Map;  
  4. import java.util.concurrent.DelayQueue;  
  5. import java.util.concurrent.ExecutorService;  
  6. import java.util.concurrent.Executors;  
  7. import java.util.concurrent.TimeUnit;  
  8. import java.util.concurrent.atomic.AtomicLong;  
  9.   
  10. /** 
  11.  * 延时队列管理类,用来添加任务、执行任务 
  12.  *  
  13.  * @author whd 
  14.  * @date 2017年9月25日 上午12:44:59 
  15.  */  
  16. public class DelayOrderQueueManager {  
  17.     private final static int DEFAULT_THREAD_NUM = 5;  
  18.     private static int thread_num = DEFAULT_THREAD_NUM;  
  19.     // 固定大小线程池  
  20.     private ExecutorService executor;  
  21.     // 守护线程  
  22.     private Thread daemonThread;  
  23.     // 延时队列  
  24.     private DelayQueue<DelayOrderTask<?>> delayQueue;  
  25.     private static final AtomicLong atomic = new AtomicLong(0);  
  26.     private final long n = 1;  
  27.     private static DelayOrderQueueManager instance = new DelayOrderQueueManager();  
  28.   
  29.     private DelayOrderQueueManager() {  
  30.         executor = Executors.newFixedThreadPool(thread_num);  
  31.         delayQueue = new DelayQueue<>();  
  32.         init();  
  33.     }  
  34.   
  35.     public static DelayOrderQueueManager getInstance() {  
  36.         return instance;  
  37.     }  
  38.   
  39.     /** 
  40.      * 初始化 
  41.      */  
  42.     public void init() {  
  43.         daemonThread = new Thread(() -> {  
  44.             execute();  
  45.         });  
  46.         daemonThread.setName("DelayQueueMonitor");  
  47.         daemonThread.start();  
  48.     }  
  49.   
  50.     private void execute() {  
  51.         while (true) {  
  52.             Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();  
  53.             System.out.println("当前存活线程数量:" + map.size());  
  54.             int taskNum = delayQueue.size();  
  55.             System.out.println("当前延时任务数量:" + taskNum);  
  56.             try {  
  57.                 // 从延时队列中获取任务  
  58.                 DelayOrderTask<?> delayOrderTask = delayQueue.take();  
  59.                 if (delayOrderTask != null) {  
  60.                     Runnable task = delayOrderTask.getTask();  
  61.                     if (null == task) {  
  62.                         continue;  
  63.                     }  
  64.                     // 提交到线程池执行task  
  65.                     executor.execute(task);  
  66.                 }  
  67.             } catch (Exception e) {  
  68.                 e.printStackTrace();  
  69.             }  
  70.         }  
  71.     }  
  72.   
  73.     /** 
  74.      * 添加任务 
  75.      *  
  76.      * @param task 
  77.      * @param time 
  78.      *            延时时间 
  79.      * @param unit 
  80.      *            时间单位 
  81.      */  
  82.     public void put(Runnable task, long time, TimeUnit unit) {  
  83.         // 获取延时时间  
  84.         long timeout = TimeUnit.NANOSECONDS.convert(time, unit);  
  85.         // 将任务封装成实现Delayed接口的消息体  
  86.         DelayOrderTask<?> delayOrder = new DelayOrderTask<>(timeout, task);  
  87.         // 将消息体放到延时队列中  
  88.         delayQueue.put(delayOrder);  
  89.     }  
  90.   
  91.     /** 
  92.      * 删除任务 
  93.      *  
  94.      * @param task 
  95.      * @return 
  96.      */  
  97.     public boolean removeTask(DelayOrderTask task) {  
  98.   
  99.         return delayQueue.remove(task);  
  100.     }  
  101. }  

 

测试类

 

[java] view plain copy
 
  1. package com.delqueue;  
  2.   
  3. import java.util.concurrent.TimeUnit;  
  4.   
  5. import com.test.delayqueue.DelayOrderQueueManager;  
  6. import com.test.delayqueue.DelayOrderWorker;  
  7.   
  8. public class Test {  
  9.     public static void main(String[] args) {  
  10.         DelayOrderWorker work1 = new DelayOrderWorker();// 任务1  
  11.         DelayOrderWorker work2 = new DelayOrderWorker();// 任务2  
  12.         DelayOrderWorker work3 = new DelayOrderWorker();// 任务3  
  13.         // 延迟队列管理类,将任务转化消息体并将消息体放入延迟对列中等待执行  
  14.         DelayOrderQueueManager manager = DelayOrderQueueManager.getInstance();  
  15.         manager.put(work1, 3000, TimeUnit.MILLISECONDS);  
  16.         manager.put(work2, 6000, TimeUnit.MILLISECONDS);  
  17.         manager.put(work3, 9000, TimeUnit.MILLISECONDS);  
  18.     }  
  19.   
  20. }  


OK 这就是项目中的具体使用情况,当然具体内容被忽略,整体框架就是这样,还有这里使用java的延时队列但是这种方式是有问题的如果如果down机则会出现任务丢失,所以也可以考虑使用mq、redis来实现

 

posted @ 2018-03-07 22:47  BarryW  阅读(41000)  评论(1编辑  收藏  举报