springboot定时任务

文章详细介绍了SpringBoot中如何使用@Scheduled注解配合cron表达式创建定时任务,包括如何在项目启动时执行、每小时特定时间执行以及重试机制。还展示了如何处理任务执行结果并决定重试策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间轮算法

round型时间轮:任务上记录一个round,遍历到了就将round减一,为0时取出执行
分层时间轮:使用多个不同时间维度的轮(天轮:记录几点执行;月轮:记录几号执行),cron表达式

Timer

每个任务在单独的线程中执行,默认情况下,所有任务共享一个后台线程。如果该线程因任务异常而终止,所有定时任务将不再执行
在任务延迟或执行时间较长时,可能会导致后续任务的延迟
Timer只能固定时间或者一定延迟执行
对于简单的定时任务,Timer 可能足够用

@Component
public class TimerTest {
    @PostConstruct
    public void task1(){
        Timer timer = new Timer();//任务启动
        for (int i=0; i<2; i++){
            TimerTask timerTask = new FooTimeTask("foo"+i);
            //schedule真正执行时间取决于上一个任务结束时间
            //下一个任务的执行时间是基于上一个任务的完成时间
            //任务的执行是顺序的,适合任务之间有依赖关系的情况
            //如果上一个任务运行时间较长,下一个任务会延迟执行,确保任务按顺序完成
            //TimerTask task, Date firstTime, long period
            timer.schedule(timerTask,new Date(),2000);//任务添加

			//scheduleAtFixedRate严格按照预设时间执行
			//下一个任务的执行时间是基于初始调度时间加上指定的周期
			//执行的任务,delay是延迟时间,period 是任务执行的间隔
			//任务会尽可能按固定频率执行。如果上一个任务执行时间超过了预定的周期,可能会导致任务重叠执行。
            //timer.scheduleAtFixedRate();
        }
    }
}

//具体任务
public class FooTimeTask extends TimerTask {
    private String name;
    public FooTimeTask(String name){
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println("name=" + name + ",startTime" + new Date());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("name=" + name + ",startTime" + new Date());
    }
}

ScheduledThreadPool定时任务线程池

可以设置线程池的大小,支持多线程执行任务,任务的执行不会受到单个任务失败的影响,对于需要高并发、灵活性和更强错误处理能力的场景,ScheduledThreadPoolExecutor 更加合适
可以有效地管理多个并发任务,避免因任务执行时间过长而影响调度

@Component
public class ScheduleThreaPoolTest {
    @PostConstruct
    public void task1(){
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        //在前一个任务完成后,延迟指定的时间再开始下一个任务
        scheduledExecutorService.scheduleWithFixedDelay(new Task("task"), 0, 2, TimeUnit.SECONDS);

        //因为 run 方法的执行时间(3 秒)超过了调度间隔(2 秒),这可能会导致任务堆积,后续任务在前一个任务完成后才能执行
        //scheduledExecutorService.scheduleAtFixedRate(new Task("task"), 0, 2, TimeUnit.SECONDS);
    }
    }
}

class Task implements Runnable{
    private String name;
    public Task(String name){
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println("name=" + name + ",startTime" + new Date());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("name=" + name + ",endTime" + new Date());
        }

}

定时任务

@Scheduled注解cron解析

@Scheduled(cron = "0 0 * * * *")
秒、分钟、小时、日期、月份、星期和年份(可选)

其中每个字段都可以使用以下方式来指定取值:
单个值:例如 0、5、10。
多个值:通过逗号分隔,例如 1,3,5。
范围:通过连字符 - 指定范围,例如 1-5。
通配符 *:表示匹配所有可能的值,例如 * 表示每秒、每分、每时等。
步长 /:用于指定取值的步长,例如 */5 表示每隔 5 个单位执行一次。
枚举:通过逗号分隔多个值,可以与范围或步长结合使用,例如 1-3,5-7/2。

下面是一些示例:
0 0 12 * * ?:在每天的中午 12 点执行任务。
0 15 10 ? * MON-FRI:在周一至周五的上午 10 点 15 分执行任务。
0 0/30 9-17 * * MON-FRI:在周一至周五的上午 9 点到下午 5 点之间,每隔 30 分钟执行任务。
0 0 23 L * ?:在每月最后一天的晚上 11 点执行任务。

@Scheduled与@PostConstruct注解

@Component
public class TaskA {
    Log log = LogFactory.getLog(TaskA.class);
    /**
     * @Scheduled注解可以创建定时任务
     * @PostConstruct注解启动项目时定时任务会被执行
     */
    //@PostConstruct
    @Scheduled(cron = "0 5/20 * * * *")
    public void Task1() {
        saveTAlarmData();
    }
    public void saveTAlarmData(){}
	
	/**
     * @Scheduled+@PostConstruct
     * 注解,会在启动项目时和@Scheduled配置的时间执行
     */
	@PostConstruct
    @Scheduled(cron = "0 5/20 * * * *")
    public void Task1() {
        saveTAlarmData();
    }
}

@EnableScheduling注解
开启定时任务的执行,此时spring容器才可以识别@Scheduled标注的方法,然后自动定时执行。

// 开始定时任务注解
@EnableScheduling
@SpringBootApplication()
@Slf4j
public class SpringbootApplication {
	public static void main(String[] args){
		log.info("启动后端工程在端口1900");
		SpringApplication.run(SpringbootApplication.class, args);
	}
}

重试定时任务
1,根据定时任务执行结果决定是否在特定时间后重试定时任务
2,设置最大重试次数
3,当重试次数达到最大次数后,放弃重试

@Component
public class TGridData {
    Log log = LogFactory.getLog(TGridData.class);
    
    private int retryCount = 0;
    private final Timer timer = new Timer();
    
    private IGridDataService gridData;
    @Autowired
    public TGridData(IGridDataService gridData) {
        if (gridData != null){
            this.gridData = gridData;
        }
    }

	/**
     * 定时任务;每小时第2,8,15分钟执行一次
     * 例如当第2分钟执行时,执行结果不是true,每隔2分钟会重试一次直至执行成功
     */
    @PostConstruct
    @Scheduled(cron = "0 2,8,15 * * * *")
    public void taskPre1() {
        save1();
    }

    public void save1() {
        if (gridData != null){
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
                Date dt = new Date();
                String times = sdf.format(dt);
                times += "0000";
                boolean res = gridData.saveOtherGridFromTQ(times);
                if (!res) {
                  int MAX_RETRIES = 3;
                  if (retryCount < MAX_RETRIES) {
                        scheduleRetry();
                    } else {
                        log.warn("Exceeded maximum retry attempts. Giving up.");
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    /**
     * 根据saveOtherGridFromTQ(times)执行结果是否为true,决定是否触发重试机制
     * 重试一次retryCount加1,最多重试3次
     */
    private void scheduleRetry() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                retryCount++;
                save1();
            }
        }, TimeUnit.SECONDS.toMillis(120));
    }
}

以上定时任务中gridData.saveOtherGridFromTQ(times)方法,全部类型资料收到后方法才会返回true

@Override
    public boolean saveOtherGridFromTQ(String times) {
        ......
        boolean isTairFound = false;
        boolean isQairFound = false;
        boolean isWindFound = false;
        boolean isUwinFound = false;
        boolean isVwinFound = false;
        for (NafpHrcldasMul1kmRtRetData.NafpHrcldasMul1kmRtRetDataItem next : ds) {
            String prodCount = next.getProdCount();
            if (Objects.equals(prodCount, "TAIR")){
                log.info("本次天擎返回结果包含TAIR");
                isTairFound = true;
            } else if (Objects.equals(prodCount, "QAIR")) {
                isQairFound = true;
                log.info("本次天擎返回结果包含QAIR");
            } else if (Objects.equals(prodCount, "WIND")) {
                isWindFound = true;
                log.info("本次天擎返回结果包含WIND");
            } else if (Objects.equals(prodCount, "UWIN")) {
                isUwinFound = true;
                log.info("本次天擎返回结果包含UWIN");
            } else if (Objects.equals(prodCount, "VWIN")) {
                isVwinFound = true;
                log.info("本次天擎返回结果包含VWIN");
            }
        }    
        return isTairFound && isQairFound && isWindFound && isUwinFound && isVwinFound;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值