执行分段延时轮训任务

文章目录


背景

简单介绍下需求背景:

我对设备下发了一个指令,但是这个执行内容比较复杂,可能需要很久的时间才能完成,所以下发指令完成后,我需要轮询这个指令执行的状态,由于指令执行是一个过程,所以有多个状态,需要根据状态决定下次轮询间隔,直到给出结果(成功/失败都算结果)后,轮询结束;


一、什么是轮询

我去卖店买个可乐,店家说现在没有进货去了,很快就会到,我要了卖店的手机号,然后我就回家了。回家后,我隔一段时间(5分钟)就给店家打电话问,饮料来了吗?

我隔一段时间(10分钟)就给店家打电话问,饮料来了吗?

我隔一段时间(30分钟)就给店家打电话问,饮料来了吗?

我隔一段时间(60分钟)就给店家打电话问,饮料来了吗?

店家最终跟我说: 1.来了你来取吧 2.没有,今天饮料不能到了

这就是轮询

二、如何实现分段延时轮询

这里的分段指的是 根据状态,改变下次轮询的执行时间

这里大致说下思路:

  • 为了不影响主线程任务,这里采用新开启一个线程处理这个轮询
  • 开启这个任务线程后,每次执行一个延时任务,或者利用sleep 执行分段延时任务
  • 此场景中,每个任务必须等待上一个任务完成,判断状态,然后决定是否执行,以及执行的下个时间间隔
  • 当轮询过程中,执行状态已经明确给出结果(执行成功/执行失败),那么应该立即结束当前轮询任务

直接上代码

java 复制代码
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class Test {

    @SneakyThrows
    public static void main(String[] args) {

        log.info("开始执行主线程任务...");

        // 执行次数
        AtomicInteger atomicInteger = new AtomicInteger(0);
        // 是否需要取消任务
        AtomicBoolean shouldCancel = new AtomicBoolean(false);

        // 开始任务轮训
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        try {
            for (int i = 0; i < 6; i++) {
                int decrement = atomicInteger.getAndIncrement();
                // 取消任务
                if (shouldCancel.get()) {
                    executorService.shutdownNow();
                    break;
                }
                Future<String> submit = executorService.submit(() -> {
                    log.info("子线程任务执行中...{}", DateUtil.now());
                    // 模拟返回任务状态
                    return RandomUtil.randomNumbers(2);
                });
                String res = submit.get();
                log.info("任务执行结果:{}", res);
                // 模拟任务执行成功/执行失败,其他状态为任务执行中间态,需要继续轮询
                if (res.equals("12") || res.equals("13")) {
                    executorService.shutdownNow();
                    shouldCancel.set(Boolean.TRUE);
                }
                // 等待时间 单位: 分 (这里为了加速,用秒)
                Thread.sleep(NumberUtil.pow(2, decrement).toBigInteger().longValue() * 1000);
    //            Thread.sleep(NumberUtil.pow(2, decrement).toBigInteger().longValue() * 1000 * 60);
                if (submit.isDone()) {
                    log.info("任务执行完毕...");
                }
            }
            executorService.shutdown();
            boolean over = !executorService.awaitTermination(70, TimeUnit.SECONDS);
            if (over) {
                executorService.shutdownNow(); // 如果等待超时,则取消所有未完成的任务
                System.out.println("结束");
                if (over)
                    System.err.println("线程池没有终止");
            }
        } finally {
            executorService.shutdown();
        }
    }

}

可以看到任务都是等待上一个任务执行,并且时间间隔也是按照我们的预想在一个个执行


总结

至此,就满足了这样一个分段延时的轮询任务的执行,而且这里是异步执行,主线程会立即返回,用户无感知的后台轮询,也节省了使用定时任务不停轮询造成的开销;
这里还有一个不用定时任务的原因:
除了上述说的造成开销外,就是定时任务有一定延迟,你如果设置定时任务的时间太短,那么服务器压力陡增,如果时间过长,那么会导致有一定的延迟,所以综合考量,都是这种方式更加合理

相关推荐
牛奔1 小时前
Go 如何打印调试深层或嵌套的结构体
开发语言·后端·golang
geovindu1 小时前
go: Iterative Algorithms
开发语言·后端·算法·golang·迭代算法
学逆向的1 小时前
汇编——JCC指令
开发语言·汇编·网络安全
mayaairi2 小时前
JS循环语句深度解析:嵌套for、while与do...while
开发语言·前端·javascript
前端工作日常3 小时前
我学习到的Java中domain和dto区别
java·后端
kite01213 小时前
Go语言Map深度解析与最佳实践
开发语言·后端·golang
码农学院3 小时前
基于Spring Boot的跨境电商多语言订单管理系统架构设计
java·大数据·spring boot
技术小结-李爽4 小时前
【工具】pom文件的<packaging>
java·maven
l156469484 小时前
突围!图文混合知识库难以解析,Kimi-K3 原生视觉架构深耕知识工作,DMXAPI 统一接口,缩短项目开发周期
java·大数据·开发语言
charlie1145141915 小时前
现代C++工程实践 WeakPtr 实战(三):WeakPtrFactory 与「最后成员」惯用法
开发语言·c++·开源项目·现代c++