DelayQueue延时任务队列总结和实践

DelayQueue里面是一个无界的BlockingQueue,且有一个优先级队列
DelayQueue延时任务队列总结和实践_第1张图片
而且该优先级队列里面的实现是堆排序,这样每插入一个新的任务,都可以立即调整任务的顺序。

DelayQueue里面放的元素必须为实现了Delayed接口的任务。
里面有两个核心方法

getDelay 用于判断任务是否到期,如果是返回-1 表示任务已经到期

我这里用**(创建时间+延期时间-当前时间)**来做判断
compareTo 方法,这个是比较器,用于对队列的元素进行排序
返回值大于0的排在后面。

我这里用的是 距离即将执行的时刻最近的任务排在最前面。
即比较两个任务的 (创建时间+延期时间-当前时间) 来判断

当然compareTo方法的实现也可以自己随意定义,比如按先来先执行,按延期时间长短等等。

 static class Worker implements Delayed {

        private int id;

        private String body; // 消息内容
        private long start = System.currentTimeMillis();//创建时刻的时间
        private long excuteTime;// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。

        // 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期
        // 否则还没到期
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert((start + this.excuteTime) - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        /**
         *比较时间,以最接近执行时间的任务,排在最前面
         */
        @Override
        public int compareTo(Delayed delayed) {
            Worker msg = (Worker) delayed;
            return (int)(((start + this.excuteTime) - System.currentTimeMillis()) -((msg.start + msg.excuteTime) - System.currentTimeMillis())) ;
        }

        public int getId() {
            return id;
        }

        public String getBody() {
            return body;
        }


        public Worker(int id, String body, long excuteTime) {
            this.id = id;
            this.body = body;
            this.excuteTime = excuteTime;
        }
    }

然后实现消费者线程,不停的从阻塞队列里面取任务,如果任务没到期 是取不出来的,该线程会进入wait状态,这里的关键点就在于,wait是有时间参数的wait,wait的是该任务离到期时间的时间间隔。 如果到期,则取出来执行。
这个wait是ReentrantLock的condition的方法下的wait。 和object的wait不太一样。
DelayQueue延时任务队列总结和实践_第2张图片
内部实现的关键点: 当一个线程get元素的时候,发现元素未到期,此时进入wait状态,那么到期之后,是谁来唤醒?

可以看到wat(时间参数) 这种方法是个native此方法,虚拟机底层来实现,那猜测,时间到了的唤醒也是虚拟机底层来实现。

在这里插入图片描述

但是这里用的是awaitNanos(时间参数),该方法则是使用 LockSupport.parkNanos(this, nanosTimeout); 来实现的线程挂起。
这个parkNanos的底层又是 UNSAFE.park(false, nanos);
这个方法则是native原生方法了。

DelayQueue延时任务队列总结和实践_第3张图片

 static class Consumer implements Runnable {
        // 延时队列 ,消费者从其中获取消息进行消费
        private DelayQueue queue;

        public Consumer(DelayQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Worker take = queue.take();
                    System.out.println("消费消息id:" + take.getId() + " 消息体:" + take.getBody()+":"+System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

启动
 public static void main(String[] args) throws InterruptedException {

        /// 创建延时队列
        DelayQueue queue = new DelayQueue();
        //将延时消息放到延时队列中
        // 启动消费线程 消费添加到延时队列中的消息,前提是任务到了延期时间
        ExecutorService exec = Executors.newFixedThreadPool(1);
        exec.execute(new Consumer(queue));
        Worker m1 = new Worker(1, "world", 5000);
        queue.offer(m1);

        Long world=System.currentTimeMillis();
        System.out.println("放入world:"+world);
        Thread.sleep(3000);

        Long hello=System.currentTimeMillis();

        Worker m2 = new Worker(2, "hello", 3000);
        System.out.println("放入hello:"+System.currentTimeMillis());
        System.out.println("间隔:"+(hello-world));
        queue.offer(m2);

        Worker m3 = new Worker(3, "cao", 1000);
        queue.offer(m3);

        exec.shutdown();
    }


m1 延迟5秒, 然后过了3秒钟之后 放了m2延迟3秒, 此时离m1要执行只剩下2秒了
然后放入m3延期1秒, 那么即将执行的是m3 1秒 其次是m1 2秒 然后是m2 三秒
输出结果如下!
DelayQueue延时任务队列总结和实践_第4张图片

场景:
需要延迟删除保险明细数据 和延迟回填保单信息。
这里是两个任务类型,需要元素封装需要有Type

以下为封装到项目里面的工具类

/**
 * @ClassName DelayWorkUtil
 * @Author laixiaoxing
 * @Date 2019/5/29 下午9:48
 * @Description 简易延时队列  只能执行秒级延时任务,如果是长时间且重要的延时任务 请勿使用该类
 * 因为该类没有异常恢复机制,如果丢失了任务就没有了!!
 * @Version 1.0
 */
@Component
@Slf4j
public class DelayWorkTask implements InitializingBean {

    @Autowired
    private InsuranceDOMapper insuranceDOMapper;

    @Autowired
    private InsuranceDao insuranceDao;

    private DelayQueue delayQueue = new DelayQueue();

    public boolean putWork(Worker worker) {

        return delayQueue.offer(worker);
    }


    /**
     * 所有的bean初始化完成的时候加载该方法
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
    //固定大小为1的线程池
        ExecutorService exec = Executors.newFixedThreadPool(1);
        exec.execute(new Consumer(delayQueue));
    }

    
    //消费者线程
    class Consumer implements Runnable {
        // 延时队列 ,消费者从其中获取消息进行消费
        private DelayQueue queue;

        public Consumer(DelayQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) {
                try {

                    Worker take = queue.take();

                    if (WorkerType.DELETE_INSURANCE.getCode().equals(take.getWorkerType())) {
    
                        int num = insuranceDOMapper.deleteByAccountNo((String) take.getBody());
                        if (num > 0) {
                            log.info("删除校验失败对账单对应的保险明细成功");
                        } else {
                            log.info("延迟15秒都没接到数据,删除失败");
                        }
                    }


                    if ((WorkerType.BACKFILL.getCode().equals(take.getWorkerType())) {
                        BackFullDTO backFullDTO = (BackFullDTO) take.getBody();

                        log.info("回填保单信息accountNo{},checkStatus{},MatchTime{}", backFullDTO.getAccountNo(),
                                backFullDTO.getCheckStatus(), backFullDTO.getMatchTime());
                        //回填保单信息
                        InsuranceQueryDTO insuranceQueryDTO = new InsuranceQueryDTO();
                        insuranceQueryDTO.setAccountNo(backFullDTO.getAccountNo());
                        List insuranceDOs = insuranceDao.getInsurance(insuranceQueryDTO);
                        //统一修改保单信息
                        for (InsuranceDO item : insuranceDOs) {
                            item.setCheckStatus(backFullDTO.getCheckStatus());
                            item.setMatchTime(backFullDTO.getMatchTime());
                            insuranceDao.updateByPrimaryKeySelective(item);
                        }
                    }

                } catch (InterruptedException e) {
                    log.error("延时任务出现异常",e);
                }
            }
        }
    }
}

任务元素

public  class Worker implements Delayed {

    private T body; // 消息内容

    private long start = System.currentTimeMillis();//创建时刻的时间

    private long excuteTime;// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。

    private String WorkerType;

    // 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期
    // 否则还没到期
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert((start + this.excuteTime) - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 比较时间,以最接近执行时间的任务,排在最前面
     */
    @Override
    public int compareTo(Delayed delayed) {
        Worker msg = (Worker) delayed;
        return (int) (((start + this.excuteTime) - System.currentTimeMillis()) - ((msg.start + msg.excuteTime) - System
                .currentTimeMillis()));
    }


    public T getBody() {
        return body;
    }

    public String getWorkerType() {
        return WorkerType;
    }

    public Worker(T body, long excuteTime, String workerType) {
        this.body = body;
        this.excuteTime = excuteTime;
        WorkerType = workerType;
    }
}

使用时候:
加粗样式

上面的这个类是一个线程处理多个延时任务。 在任务不多的时候可以这样处理。
但是任务很多的时候,还是需要一个类型的任务单独一个线程去处理。

可以封装成传入的参数是一个线程

你可能感兴趣的:(java)