原文:https://www.cnblogs.com/domi22/p/9418433.html
1、spring boot使用注解实现定时器
@EnableScheduling
@Component
public class TestTimer {
@Scheduled(cron="*/1 * * * * ?")
public void testOne() {
System.out.println("第一个"+"每1秒执行一次"+Thread.currentThread().getName());
}
@Scheduled(fixedRate=1000)
public void testTwo() {
System.out.println("第二个"+"每1秒执行一次"+Thread.currentThread().getName());
}
}
这样项目启动的时候定时器就启动了,但是通过打印线程名可以发现,这两个任务是同一个线程。
如果将第二个任务睡眠10秒,模拟延时。
@EnableScheduling
@Component
public class TestTimer {
@Scheduled(cron="*/1 * * * * ?")
public void testOne() {
System.out.println("第一个"+"每1秒执行一次"+Thread.currentThread().getName());
}
@Scheduled(fixedRate=1000)
public void testTwo() {
System.out.println("第二个"+"每1秒执行一次"+Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
会发现不但自己的任务丢失了,也造成了其他任务的丢失。
问题1:单线程任务丢失
解决办法:采用异步的方式执行调度任务,配置 Spring 的 @EnableAsync,在执行定时任务的方法上标注 @Async配置任务执行池
@EnableScheduling
@EnableAsync
@Component
public class TestTimer {
@Async
@Scheduled(cron="*/1 * * * * ?")
public void testOne() {
System.out.println("第一个"+"每1秒执行一次"+Thread.currentThread().getName());
}
@Async
@Scheduled(fixedRate=1000)
public void testTwo() {
System.out.println("第二个"+"每1秒执行一次"+Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意:@Async所使用的线程池容量为100
3.配置线程池大小
虽然上面的方式已经解决了我们的问题,但是总觉得不太好,有时候我们需要异步执行任务,但是又不需要这么多的线程的时候,我们可以使用下面的配置来设置线程池的大小
配置文件:
@Configuration
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor1() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
问题2:在分布式系统下,重复执行的问题。
解决方式1:可以使用redis的分布式锁保证spring schedule集群只执行一次。 redis分布式锁是通过setnx命令实现的。该命令的作用是,当往redis中存入一个值时,会先判断该值对应的key是否存在,如果存在则返回0,如果不存在,则将该值存入redis并返回1。(但是在分布式跨时区部署的时候,依然无法避免重复执行)
解决方式2:可以通过使用shedlock将spring schedule上锁。详细见:https://segmentfault.com/a/1190000011975027
问题3:如果使用异步多线程方式,会有并发安全问题。
如果想有的定时任务不考虑并发安全使用@Async注解,有的定时任务需要考虑线程并发使用同步阻塞方式。
@EnableScheduling
@EnableAsync
@Component
public class TestTimer {
@Scheduled(cron="*/1 * * * * ?")
public void testOne() throws InterruptedException {
System.out.println("任务1====>"+Thread.currentThread().getName());
Thread.sleep(10000); //模拟延时
}
@Async
@Scheduled(cron="*/1 * * * * ?")
public void testTwo() throws InterruptedException {
System.out.println("任务2====>"+Thread.currentThread().getName());
}
}
发现任务2还是会被任务1阻塞住,好像没什么好办法?
待解决:问题3
网友评论