spring boot 自动任务

SpringBoot自动任务
1.三种创建方式
使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:

一、基于注解(@Scheduled)
二、基于接口(实现SchedulingConfigurer接口)
三、基于注解设定多线程定时任务

2.基于注解的定时任务
2.1快速使用
创建一个定时任务配置类,ScheduleTask.java
@Configuration //1.主要用于标记配置类,兼备@Component的效果。
@EnableScheduling // 2.开启定时任务
public class ScheduleTask {
//3.添加定时任务
@Scheduled(cron = “0/5 * * * * ?”)
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
}
}

运行springboot运行类,运行结果如下

2.2@Scheduled注解
2.2.1@Scheduled注解支持的属性值
参数 参数说明 示例
cron 任务执行的cron表达式 0/1 * * * * ?
zone cron表达时解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析 GMT-8:00
fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms
1000
fixedDelayString 上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析 PT15M
fixedRate 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务 2000
fixedRateString 与fixedRate逻辑一致,只是使用java.time.Duration#parse解析 PT15M
initialDelay 首次任务执行的延迟时间 1000
initialDelayString 首次任务执行的延迟时间,使用java.time.Duration#parse解析 PT15M
2.2.2 cron详解
配置格式:
[秒] [分] [小时] [日] [月] [周] [年]

配置说明:
序号 说明 是否必填 允许填写的值 允许的通配符
1 秒 是 0-59  - * /
2 分 是 0-59  - * /
3 小时 是 0-23  - * /
4 日 是 1-31  - * ? / L W
5 月 是 1-12 or JAN-DEC  - * /
6 周 是 1-7 or SUN-SAT  - * ? / L #
7 年 否 empty 或 1970-2099   - * /

通配符说明:
(1)* 表示所有值. 例如:在分的字段上设置 “*”,表示每一分钟都会触发。

(2)? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?

(3)- 表示区间。例如在小时上设置 “10-12”,表示 10,11,12点都会触发。

(4), 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发

(5)/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。在月字段上设置’1/3’所示每月1号开始,每隔三天触发一次。

(6)L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"

(7)W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,“W"前只能设置具体的数字,不允许区间”-")

(8)# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了)

常用配置举例:
配置 说明
0 0 12 * * ?  每天12点触发
0 15 10 ? * *  每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ?  每天下午的 2点到2点59分(整点开始,每隔5分触发) 
每天下午的 18点到18点59分(整点开始,每隔5分触发)
0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 月的第三周的星期五开始触发

3.基于接口的定时任务
3.1 快速使用
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
Runnable runnable= new Runnable() {
@Override
public void run() {
System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime());
}
};

    Trigger trigger = new Trigger() {
        @Override
        public Date nextExecutionTime(TriggerContext triggerContext) {
            //可以从数据库中动态获取
            String cron = "0/5 * * * * ?"; 
            //返回执行周期(Date)
            return new CronTrigger(cron).nextExecutionTime(triggerContext);
        }
    };

    taskRegistrar.addTriggerTask(runnable,trigger);
}

}
运行springboot运行类,运行结果如下

3.2 动态调整执行周期
以上demo中,String cron = “0/5 * * * * ?”,执行周期是直接定死在代码中,如果要修改执行周期,需要修改代码,重启服务。
通常如果需要热修改,一般会将cron存储在数据库中,从数据库动态获取,这样如果需要修改执行周期,直接修改数据库,或者在Controller中提供一个修改库中cron字段的接口,调用controller来修改cron字段的值。这样就可以实现热修改执行周期。

4.基于注解的多线程定时任务
4.1快速使用
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask {
@Async
@Scheduled(fixedDelay = 1000) //间隔1秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}

@Async
@Scheduled(fixedDelay = 2000)
public void second() {
    System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
    System.out.println();
}

}

运行springboot运行类,运行结果如下

4.2@Async
4.2.1 @Async的用法
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

4.2.2 Future类型返回值
@Async中支持两种返回值,void和Future。
Void返回值在demo中已经展示了用法,此处就不再多加赘述。
Future作为返回值,代码中常用new AsyncResult<返回类型>(返回值)来返回。
AsyncResult类实现了ListenableFuture接口,ListenableFuture实现了Future接口。
示例代码如下:
@Async
@Override
public Future asyncTaskFuture() throws Exception {
System.out.println(“异步线程,线程名:” + Thread.currentThread().getName());
System.out.println(“异步处理方法-----start-------asyncTaskFuture—”);
int k = 1;
Thread.sleep(1000);
System.out.println(“异步处理方法-----end---------asyncTaskFuture—”);
return new AsyncResult (String.valueOf(k));
}

Future接口定义了如下几个抽象方法

(1)isDone()
返回Boolean类型值,用来判断该异步任务是否执行完成,如果执行完成,则返回true,如果未执行完成,则返回false.
(2)cancel(boolean mayInterruptRunning) 
返回boolean类型值,参数也是一个boolean类型的值,用来传入是否可以打断当前正在执行的任务。
如果参数是true且当前任务没有执行完成 ,说明可以打断当前任务,那么就会返回true。如果当前任务还没有执行,那么不管参数是true还是false,返回值都是true。
如果当前任务已经完成,那么不管参数是true还是false,那么返回值都是false。
如果当前任务没有完成且参数是false,那么返回值也是false。
总结下来就是:
1.如果任务还没执行,那么如果想取消任务,就一定返回true,与参数无关。
2.如果任务已经执行完成,那么任务一定是不能取消的,所以此时返回值都是false,与参数无关。
3.如果任务正在执行中,那么此时是否取消任务就看参数是否允许打断(true/false)。

(3)isCancelled()
 返回的是boolean类型,如果是上面总结的第三种情况,这才是真正意义上有机会被取消的任务,那么此时如果上面的方法返回的是true,那么说明任务取消成功了,则这个方法返回的也就是true。

(4)get()
返回的是在异步方法中最后return 的那个对象中的value的值。主要是通过里面的get()方法来获取异步任务的执行结果,这个方法是阻塞的,直到异步任务执行完成。

(5)get(long timeout,TimeUnit unit)
这个方法和get()的功能是一样的(在方法执行没有超时的情况下效果是一样的),只不过这里参数中设置了超时时间,因为get()在执行的时候是需要等待回调结果的,是阻塞在那里的,如果不设置超时时间,它就阻塞在那里直到有了任务执行完成。我们设置超时时间,就可以在当前任务执行太久的情况下中断当前任务,释放线程,这样就不会导致一直占用资源。参数一是时间的数值,参数二是参数一的单位,可以在TimeUnit这个枚举中选择单位。如果任务执行超时,则抛出TimeOut异常,返回的message就是null。

4.2.3 使用Future返回值控制流程
有了Future接口提供的五个方法,我们就可以在代码中进行流程控制。示例代码如下:
@RestController
@RequestMapping("/v3")
public class FutureController {
@Autowired
private FutureService futureService;

@RequestMapping("/test")
public String isDoneTest() throws InterruptedException, ExecutionException {
    System.out.println("开始访问");
    long l1 = System.currentTimeMillis();
    Future<String> r1 = futureService.JobOne();
    Future<String> r2 = futureService.JobTwo();
    Future<String> r3 = futureService.JobThree();
    while(true) {
        if(r1.isDone() && r2.isDone() && r3.isDone()) {
            //如果异步方法全部执行完,跳出循环
            break;
        }
        Thread.sleep(2000);//每隔2000毫秒判断一次
    }
    long l2 = System.currentTimeMillis();//跳出while循环时说明此时三个异步调用的方法都执行完成了,此时得到当前时间

    String result = r1.get();
    System.out.println("结束访问,用时"+(l2-l1));
    System.out.println("使用get方法获得的返回内容:"+result);
    return "finished";
}

}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值