DelayedQueue的学习
参考链接:http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
1.DelayedQueue是一个无界的阻塞队列,其内的元素是实现了Delayed接口的元素。
Delayed,一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序。
getDelay的方法返回时剩余的延迟时间。
创建一个Delayed接口的实现类,该元素是要放入到QueueDelayed队列中的。
1 package com.zimo.mybaties.delayed; 2 3 import org.springframework.transaction.annotation.Transactional; 4 5 import java.util.concurrent.Delayed; 6 import java.util.concurrent.TimeUnit; 7 8 public class MyDelayedEvent implements Delayed{ 9 //要执行的任务 10 private Task task; 11 12 private Long endTime; 13 14 public MyDelayedEvent(Task task, Long endTime) { 15 this.task = task; 16 this.endTime = endTime; 17 } 18 19 //获取剩余的时间,为0获取负数时取出 20 //TimeUnit.NANOSECONDS 毫微妙 21 @Override 22 public long getDelay(TimeUnit unit) { 23 // return unit.convert(endTime,TimeUnit.MILLISECONDS) - unit.convert(System.currentTimeMillis(),TimeUnit.MILLISECONDS); 24 return unit.convert(endTime,TimeUnit.NANOSECONDS) - unit.convert(System.currentTimeMillis(),TimeUnit.NANOSECONDS); 25 } 26 27 @Override 28 public int compareTo(Delayed o) { 29 if (this == o) 30 return 1; 31 if (o==null) 32 return -1; 33 long diff = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); 34 return diff<0?-1:(diff==0?0:1); 35 } 36 37 public Task getTask() { 38 return task; 39 } 40 41 public void setTask(Task task) { 42 this.task = task; 43 } 44 45 public Long getEndTime() { 46 return endTime; 47 } 48 49 public void setEndTime(Long endTime) { 50 this.endTime = endTime; 51 } 52 }
其中Task是要执行的任务。创建一个Task接口,我们的任务都实现该接口,表示是一个任务调度到期后要执行的任务。
1 /** 2 * 任务 3 */ 4 public interface Task { 5 //调用该方法,则会执行任务 6 void executeTask(); 7 }
下面是我们实现的其中一个任务
1 package com.zimo.mybaties.delayed.delayedtest; 2 3 import com.zimo.mybaties.delayed.Task; 4 5 public class StudentTask implements Task{ 6 7 private Integer student; 8 9 public StudentTask(Integer student) { 10 this.student = student; 11 } 12 13 @Override 14 public void executeTask() { 15 System.out.println("学生任务执行"+student); 16 } 17 18 public Integer getStudent() { 19 return student; 20 } 21 22 public void setStudent(Integer student) { 23 this.student = student; 24 } 25 }
上面的类创建完毕后,我们需要创建一个守护线程来后台执行任务调度是否到到期的查询,时间到了后执行。getDelay()返回剩余的时间,
1 public interface MyDelayedService { 2 //插入任务调度 3 void put(MyDelayedEvent delayed); 4 //移除任务调度 5 boolean remove(MyDelayedEvent delayed); 6 //初始化该任务调度 7 void init(); 8 }
MyDelayedService的实现类
package com.zimo.mybaties.delayed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.concurrent.DelayQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @Service public class MyDelayedServiceImp implements MyDelayedService{ private static final Logger logger = LoggerFactory.getLogger(MyDelayedServiceImp.class); private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private DelayQueue<MyDelayedEvent> queue = new DelayQueue<>(); private Executor executor = Executors.newFixedThreadPool(30);//线程池,保证同一时刻执行的任务能执行s private Thread damon;//守护线程 @Override public void init(){ logger.info("初始化学生守护线程"); damon = new Thread(() -> execute()); //新建一个线程,执行execute方法 damon.setDaemon(true); //设置为守护线程 damon.setName("student queue thread"); //线程名称 damon.start(); //启动线程 } @Override public void put(MyDelayedEvent delayed){ logger.info("插入任务"); queue.put(delayed); } @Override public boolean remove(MyDelayedEvent delayed){ logger.info("移除任务"); return queue.remove(delayed); } private void execute(){ while (true){ //该线程要执行的内容 try { MyDelayedEvent delayed = queue.take(); if (delayed!=null){ logger.info("执行任务,任务执行时当前时间是 {}",TIME_FORMAT.format(delayed.getEndTime())); executor.execute(new Runnable() { //将执行的任务放入线程池,同一个时刻可能有多个任务要执行 @Override public void run() { delayed.getTask().executeTask();//执行任务 } }); } }catch (InterruptedException e){ logger.error("任务调度被中断"); } } } }
通过测试可知,设置同一时刻的多个任务调度时,在时间到了之后,会全部进行执行。
1 2018-08-28 18:04:19.020 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 2 2018-08-28 18:04:19.020 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 3 学生任务执行0 4 学生任务执行8 5 2018-08-28 18:04:19.021 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 6 2018-08-28 18:04:19.021 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 7 学生任务执行7 8 2018-08-28 18:04:19.021 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 9 学生任务执行6 10 学生任务执行5 11 2018-08-28 18:04:19.022 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 12 2018-08-28 18:04:19.022 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 13 学生任务执行4 14 2018-08-28 18:04:19.022 INFO 48417 --- [nt queue thread] c.z.mybaties.delayed.MyDelayedEventList : 执行任务,任务执行时当前时间是 2018-08-28 18:04:19 15 学生任务执行2 16 学生任务执行1
当要执行的线程数大于线程池的最大数目时,会进行等待。
?如何移除我们要取消的任务呢。
首先我们的任务是存放如QueueDelayed队列中的,在本文中。他存放于类MyDelayedServiceImp中的 queue变量中。其内存放是实现了delayed接口的元素。通过查看DelayedQueue的文档可知。
其通过remove(Object o)来移除,o是我们的元素。通过查看remove的实现可知。其内是通过equals比较两个对象是否相等来判断是否是同一个delayed。所以我们需要在我们的QueueDelayed中实现我们的equals方法,使其两个对象之间的相等能够进行判断。
内部实现:
/** * Removes a single instance of the specified element from this * queue, if it is present, whether or not it has expired. */ public boolean remove(Object o) { final ReentrantLock lock = this.lock; lock.lock(); try { return q.remove(o);//进入该方法 } finally { lock.unlock(); } }
public boolean remove(Object o) { int i = indexOf(o); if (i == -1) return false; else { removeAt(i); return true; } } private int indexOf(Object o) { if (o != null) { for (int i = 0; i < size; i++) if (o.equals(queue[i])) //比较 return i; } return -1; }
我们的实现;实现我们的Delayed元素的equals 和hashCode。
package com.zimo.mybaties.delayed; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class MyDelayedEvent implements Delayed{ //要执行的任务 private Task task; private String uniqueKey; //该uniqueKey的生成规则根据业务来进行确定。只需要确保uniqueKey是唯一标识的 private Long endTime; public MyDelayedEvent(Task task, Long endTime) { this.task = task; this.endTime = endTime; } public MyDelayedEvent(Task task, Long endTime, Integer targetClassId) { this.task = task; this.endTime = endTime; setUniqueKey(targetClassId); System.out.println("unique key : "+getUniqueKey()); } //获取剩余的时间,为0获取负数时取出 //TimeUnit.NANOSECONDS 毫微妙 @Override public long getDelay(TimeUnit unit) { // return unit.convert(endTime,TimeUnit.MILLISECONDS) - unit.convert(System.currentTimeMillis(),TimeUnit.MILLISECONDS); return unit.convert(endTime,TimeUnit.NANOSECONDS) - unit.convert(System.currentTimeMillis(),TimeUnit.NANOSECONDS); } @Override public int compareTo(Delayed o) { if (this == o) return 1; if (o==null) return -1; long diff = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); return diff<0?-1:(diff==0?0:1); } @Override public int hashCode() { final int prime = 31; //hashCode就是用的31 int result = 1; result = prime*result + endTime.hashCode(); result = prime*result + ((uniqueKey==null)?0:uniqueKey.hashCode()); //我这里因为Task也是一个对象,为了简便,所以不用task作为hashCode生成对象。而是新增加一个uniqueKey。 return result; } @Override public boolean equals(Object obj) { if (this==obj) return true; if (obj == null) return false; if (getClass()!=obj.getClass()) return false; MyDelayedEvent o = (MyDelayedEvent)obj; //Long对象的比较,判断值是否相同也是通过equals if (!getEndTime().equals(o.getEndTime())) return false; if (!getUniqueKey().equals(o.getUniqueKey())) return false; return true; } public String getUniqueKey() { return uniqueKey; } public void setUniqueKey(Integer targetClassId) { //uniqueKey生成规则 this.uniqueKey = new StringBuffer().append(task.getClass()).append(targetClassId).append(endTime).toString(); } public Task getTask() { return task; } public void setTask(Task task) { this.task = task; } public Long getEndTime() { return endTime; } public void setEndTime(Long endTime) { this.endTime = endTime; } }
后面有需要新的定时任务需求的时候,只需要新增一个实现了Task接口的实现类即可。
public class AssistantTask implements Task { @Override public void executeTask() { System.out.println("assistant task run "); } }
MyDelayedEvent delayed_assistant = new MyDelayedEvent(new AssistantTask(),nowTime+delay*1000+1000,2); myDelayedService.put(delayed_assistant);