简介
1、各种定时任务
Java集中式定时任务有很多版本:
-
while(true) + Thread.sleep
-
java.util.Timer + java.util.TimerTask
-
ScheduleExecutorService
-
Quartz:可与 Spring整合
-
Spring Task
-
Spring Boot注解:@EnableScheduling + @Scheduled,底层是 Spring Task
2、Spring定时任务
某次定时任务若出现异常,不会影响下次定时任务的执行
这个是是单线程的,假如某一个任务执行时间过长,可能导致别的执行任务到了下一个时间节点,可能导致定时任务拖延或丢失。
2.1、简介
Spring 为我们提供了异步执行任务调度的方式,提供了 TaskExecutor、TaskScheduler接口,还有两个注解 @EnableScheduling、@Scheduled。
2.2、添加注解进行使用
在启动类上添加注解 @EnableScheduling,开启定时任务。
在方法上添加注解 @Scheduled(fixedRate = 10006030),时间单位是毫秒,
该方法不需要被放在 mian函数里面也会被执行,
#
3、Spring双机定时任务
3.1、单机异步多线程
-
开启异步。在启动类上加上注解 @EnableAsync。
-
在方法上添加注解 @Async,每次触发定时任务都会新开启一个线程。
3.2、简易模拟单机下双机定时任务
3.2.1、思路
两个任务,同时只能有一个在执行,当某一个出现异常的时候,另一个顶替上,所以单机里面用的是加锁来模拟。
3.2.2、实现
private volatile boolean flag = true; int count = 0; @Scheduled(fixedRate = 1000 * 5) @Async public void remindDrink() throws InterruptedException { // 执行两次后出现异常 if (count++ < 2) { synchronized (Demo1Application.class) { if (flag) { flag = false; System.out.println("该喝水了!--线程" + Thread.currentThread().getName() + "--" + DateFormat.getDateTimeInstance().format(new Date())); flag = true; } } } else { flag = false; } } @Scheduled(fixedRate = 1000 * 5) @Async public void remindWalk() throws InterruptedException { if(flag){ TimeUnit.SECONDS.sleep(1); } synchronized (Demo1Application.class) { if (!flag) { System.out.println("该走动了!--线程" + Thread.currentThread().getName() + "--" + DateFormat.getDateTimeInstance().format(new Date())); } } }
3.3、通过中间件 Redis 设定双机定时任务
3.3.1、思路
因为是双机,那么就不能用单机下的锁了,那么就可思考,是不是可以把这个锁放到外面去,例如 redis,
当主机 1做任务的时候,就把 任务名称(key):本机信息(value) 放进去,然后设置一个大于定时任务周期的时间作为其过期时间 TTL,
那么当主机1 在的时候就能一直做任务,每次做任务的时候就再次 set 进去,那么过期时间就会被刷新,它挂了的话,主机2 是一直在尝试做任务的,但是因为 redis 这个 key 一直在所以没做,当 key 过期了不存在,就可以开始自己的任务了。
3.3.2、实现
记得添加对 redisTemplate 的 config,不然会出现乱码。
@EnableAsync @EnableScheduling @SpringBootApplication public class Demo2Application { @Autowired private RedisTemplate redisTemplate; @Value("${server.port}") private String port; private int count = 0; private String taskName = "drink"; public static void main(String[] args) { SpringApplication.run(Demo2Application.class, args); } @Scheduled(fixedRate = 1000 * 5) @Async public void remindDrink() { if (redisTemplate.hasKey(taskName) && !port.equals(redisTemplate.opsForValue().get(taskName))) { return; } // 模拟 8001做了 6次任务后掉线 if ("8001".equals(port) && count++ >= 6) { return; } redisTemplate.opsForValue().set(taskName, port, 7, TimeUnit.SECONDS); System.out.println("该喝水了!--" + port + "--线程" + Thread.currentThread().getName() + "--" + DateFormat.getDateTimeInstance().format(new Date())); } }