1 demo




​package​​ ​​com.test.domi.config;​


 


​import​​ ​​org.springframework.beans.factory.annotation.Configurable;​


​import​​ ​​org.springframework.scheduling.annotation.EnableScheduling;​


​import​​ ​​org.springframework.scheduling.annotation.Scheduled;​


​import​​ ​​org.springframework.stereotype.Component;​


​import​​ ​​java.text.SimpleDateFormat;​


​import​​ ​​java.util.Date;​


 


​@Component​


​@Configurable​


​@EnableScheduling​


​public​​ ​​class​​ ​​ScheduledTasks {​


​//每30秒执行一次​


​@Scheduled​​​​(fixedRate = ​​​​1000​​ ​​* ​​​​30​​​​)​


​public​​ ​​void​​ ​​reportCurrentTime(){​


​System.out.println (​​​​"Scheduling Tasks Examples: The time is now "​​ ​​+ dateFormat ().format (​​​​new​​ ​​Date ()));​


​}​


 


​//在固定时间执行​


​@Scheduled​​​​(cron = ​​​​"0 */1 *  * * * "​​​​)​


​public​​ ​​void​​ ​​reportCurrentByCron(){​


​System.out.println (​​​​"Scheduling Tasks Examples By Cron: The time is now "​​ ​​+ dateFormat ().format (​​​​new​​ ​​Date()));​


​}​


 


​private​​ ​​SimpleDateFormat dateFormat(){​


​return​​ ​​new​​ ​​SimpleDateFormat (​​​​"HH:mm:ss"​​​​);​


​}​



Scheduling Tasks Examples: The time is now 11:55:54
Scheduling Tasks Examples By Cron: The time is now 11:56:00
Scheduling Tasks Examples: The time is now 11:56:24
Scheduling Tasks Examples: The time is now 11:56:54
Scheduling Tasks Examples By Cron: The time is now 11:57:00


 

2 详解

cron表达式

1.cron是设置定时执行的表达式,如 0 0/5 * * * ?每隔五分钟执行一次

2.zone表示执行时间的时区

3.fixedDelay 和fixedDelayString 一个固定延迟时间执行,上个任务完成后,延迟多久执行

4.fixedRate 和fixedRateString一个固定频率执行,上个任务开始后多长时间后开始执行

5.initialDelay 和initialDelayString表示一个初始延迟时间,第一次被调用前延迟的时间

 

3 总结常见问题

a: 单线程任务丢失,转为异步线程池

默认的 ConcurrentTaskScheduler 计划执行器采用Executors.newSingleThreadScheduledExecutor() 实现单线程的执行器。因此,对同一个调度任务的执行总是同一个线程。如果任务的执行时间超过该任务的下一次执行时间,则会出现任务丢失,跳过该段时间的任务。上述问题有以下解决办法:

采用异步的方式执行调度任务,配置 Spring 的 @EnableAsync,在执行定时任务的方法上标注 @Async配置任务执行池,线程池大小 n 的数量为 单个任务执行所需时间 / 任务执行的间隔时间。如下:



​//每30秒执行一次​


​@Async​​​​(​​​​"taskExecutor"​​​​)​


​@Scheduled​​​​(fixedRate = ​​​​1000​​ ​​* ​​​​3​​​​)​


​public​​ ​​void​​ ​​reportCurrentTime(){​


​System.out.println (​​​​"线程"​​ ​​+ Thread.currentThread().getName() + ​​​​"开始执行定时任务===&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&7&&&====》"​


​+ ​​​​new​​ ​​SimpleDateFormat(​​​​"yyyy-MM-dd HH:mm:ss"​​​​).format(​​​​new​​ ​​Date()));​


​long​​ ​​start = System.currentTimeMillis();​


​Future<Boolean> isOk1;​


​Future<Boolean> isOk2;​


​。 。。。。。。。。。省略。。。。。。。​

 

b: 关于分布式情况下,重复执行的问题(两种方案)

1:可以使用redis的分布式锁保证spring schedule集群只执行一次。 redis分布式锁是通过setnx命令实现的。该命令的作用是,当往redis中存入一个值时,会先判断该值对应的key是否存在,如果存在则返回0,如果不存在,则将该值存入redis并返回1。(但是在分布式跨时区部署的时候,依然无法避免重复执行)




​@Component​


​@Configuration​


​@EnableScheduling​


​public​​ ​​class​​ ​​AutoConvertTask {​


​private​​ ​​static​​ ​​final​​ ​​Logger logger = LoggerFactory.getLogger(AutoConvertTask.​​​​class​​​​);​


 


​@Autowired​


​private​​ ​​RedisTemplate redisTemplate;​


 


​private​​ ​​static​​ ​​final​​ ​​String LOCK = ​​​​"task-job-lock"​​​​;​


 


​private​​ ​​static​​ ​​final​​ ​​String KEY = ​​​​"tasklock"​​​​;​


 


​@Scheduled​​​​(cron = ​​​​"0 0 0 * * ? "​​​​)​


​public​​ ​​void​​ ​​autoConvertJob() {​


​boolean​​ ​​lock = ​​​​false​​​​;​


​try​​ ​​{​


​lock = redisTemplate.opsForValue().setIfAbsent(KEY, LOCK);​


​logger.info(​​​​"是否获取到锁:"​​ ​​+ lock);​


​if​​ ​​(lock) {​


​List<GameHistory> historyList = historyService.findTenDaysAgoUntreated();​


​for​​ ​​(GameHistory history : historyList) {​


​update(history);​


​}​


​} ​​​​else​​ ​​{​


​logger.info(​​​​"没有获取到锁,不执行任务!"​​​​);​


​return​​​​;​


​}​


​} ​​​​finally​​ ​​{​


​if​​ ​​(lock) {​


​redisTemplate.delete(KEY);​


​logger.info(​​​​"任务结束,释放锁!"​​​​);​


​} ​​​​else​​ ​​{​


​logger.info(​​​​"没有获取到锁,无需释放锁!"​​​​);​


​}​


​}​


 


​}​


 


​}​


 

2:可以通过使用shedlock将spring schedule上锁。

 

c: 服务器宕机之后,丢失的任务如何补偿? 

可以将每次的任务执行时间缓在redis里,下次执行任务的时候都取出该时间,判断是否为上一个周期,如果不是,可以计算出中间丢失的周期数,然后做响应的补偿操作。如果怕redis宕机,可以将“执行时间”持久化到表中。