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

本文深入探讨了DelayQueue的工作原理,它是一个无界的BlockingQueue,基于堆排序实现优先级队列。元素需实现Delayed接口,通过getDelay和compareTo方法判断及排序任务。消费者线程在任务未到期时进入等待状态,等待时间由任务到期时间决定。文中还讨论了内部的wait机制,以及在不同任务场景中的应用,如延迟删除和回填信息等操作。对于大量延时任务,建议每个任务类型使用独立线程处理。

DelayQueue里面是一个无界的BlockingQueue,且有一个优先级队列
在这里插入图片描述
而且该优先级队列里面的实现是堆排序,这样每插入一个新的任务,都可以立即调整任务的顺序。

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不太一样。
在这里插入图片描述
内部实现的关键点: 当一个线程get元素的时候,发现元素未到期,此时进入wait状态,那么到期之后,是谁来唤醒?

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

在这里插入图片描述

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

在这里插入图片描述

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

        public Consumer(DelayQueue<Worker> 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<Worker> queue = new DelayQueue<Worker>();
        //将延时消息放到延时队列中
        // 启动消费线程 消费添加到延时队列中的消息,前提是任务到了延期时间
        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 三秒
输出结果如下!
在这里插入图片描述

场景:
需要延迟删除保险明细数据 和延迟回填保单信息。
这里是两个任务类型,需要元素封装需要有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<Worker> delayQueue = new DelayQueue<Worker>();

    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<Worker> queue;

        public Consumer(DelayQueue<Worker> 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<InsuranceDO> 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<T> 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;
    }
}

使用时候:
加粗样式

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

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

Spring Boot中的Delay Queue(延时队列)是一种用于处理延时任务的机制。延时任务指的是需要在一定时间后才能执行的任务。 Spring Boot中延时队列的实现主要借助了Spring的TaskScheduler来实现。TaskScheduler是Spring提供的任务调度器,可以用来执行延时任务。 为了使用延时队列,我们首先需要配置一个TaskScheduler。可以通过在配置类中添加@Bean注解来创建一个TaskScheduler的Bean。配置类内容如下: ``` @Configuration public class AppConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); scheduler.setThreadNamePrefix("TaskScheduler-"); return scheduler; } } ``` 上述配置创建了一个线程池大小为5的TaskScheduler对象,并且设置了线程名称前缀为"TaskScheduler-"。 接下来,我们可以创建一个延时任务,通过在方法上添加@Scheduled注解,并设置fixedDelay属性来定义延时的时间间隔,单位为毫秒。例如: ``` @Component public class DelayedTask { @Scheduled(fixedDelay = 5000) public void executeDelayedTask() { //延时任务的执行逻辑 System.out.println("执行延时任务"); } } ``` 上述代码中,executeDelayedTask方法使用@Scheduled注解来标识为定时任务,并设置fixedDelay为5000,表示延时5秒后执行任务。 最后,通过在启动类上添加@EnableScheduling注解来启用Spring的任务调度功能。即可实现延时任务的执行。 总结来说,Spring Boot中的Delay Queue(延时队列)是通过配置TaskScheduler来实现的。我们可以通过在方法上添加@Scheduled注解,并设置fixedDelay属性来定义延时间隔,然后在启动类上添加@EnableScheduling注解来启用任务调度功能,从而实现延时任务的执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值