时间轮算法
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;