文章目录
Spring定时任务
场景
(1)项目中有些数据需要定时处理,比如每隔一年会删除久的数据,或者每隔半年会将数据库的某些数据进行持久化保存等等对数据的处理;
(2)项目中有些任务需要使用定时任务完成,比如每隔几秒查询一下任务状态,如果任务状态变成了可执行状态那么就需要通过定时任务执行;注意的是如果任务是数据库中的某条数据,某个字段表示任务状态,那么如果部署了多个服务而且连的数据库是同一个此时使用定时任务会出现A服务创建的任务被B服务处理的情况,处理方式可以创建一个或多个字段保存服务的表示也就是A服务创建的任务有A服务的标识,任务的处理也只能让A服务处理。
其实定时任务的场景还有很多,那么怎么创建定时任务呢?Spring框架提供了创建定时任务的注解可以方便的创建定时任务,接下来我们一起学习一下吧。
应用
单线程
Spring框架提供了注解 @Scheduled
例如:下列这三个方法都是每隔5秒执行一次,首先要想执行定时任务那么必须开始定时任务,Spring框架提供了注解@EnableScheduling用于开启定时任务
@Component
@EnableScheduling
public class ConfigScheduled {
@Scheduled(fixedDelay = 5000)
public void testScheduled0() throws InterruptedException {
System.out.println(new Date() + "====>0");
Thread.sleep(3000);
}
@Scheduled(fixedRate = 5000)
public void testScheduled1() throws InterruptedException {
System.out.println(new Date() + "====>1");
Thread.sleep(3000);
}
@Scheduled(cron = "0/5 * * * * ?")
public void testScheduled2() throws InterruptedException {
System.out.println(new Date() + "====>2");
Thread.sleep(3000);
}
}
@Scheduled的属性
以上三种方法都是每隔5秒执行一次,那么有什么区别呢?
注解 @Scheduled的属性fixedDelay和fixedRate的区别
- fixedRate是事先编排好的,假如制定的任务是每隔5秒执行,那么就从0秒开始,每隔五秒执行一次,第二个任务从第5秒开始执行,不论前一个任务是否执行完毕,下一个任务到时间就执行。
- fixedDelay是事先没有编排的,是根据上一个任务执行完毕后延迟执行,假如制定的任务还是5秒执行,那么从0秒开始,若第一个任务耗时8秒,那么第二个任务就从13秒开始执行。
- cron的执行情况和fixedRate一样。
注解@Scheduled还有其他属性:
- fixedDelayString与fixedDelay功能相同使用方式不同——@Scheduled( fixedDelayString = “5000”) ,类似的fixedRateString与fixedRate——@Scheduled( fixedRateString = “5000”);
- initialDelay表示初始任务延迟时间,也就是服务启动后执行定时任务,第一个任务开始执行时间延迟时间,比如设置5秒,那么第一个任务就从5秒开始执行,而不是从0秒开始;
- zone用来设置时区,一般不用,默认使用服务部署的当地时区
注意:定时任务cron的表达式可以通过此网站创建:http://qqe2.com/cron/index
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。按顺序依次为:
秒(0~59)
分钟(0~59)
3 小时(0~23)
4 天(0~31)
5 月(0~11)
6 星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
年份(1970-2099)
其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于”月份中的日期”和”星期中的日期”这两个元素互斥的,必须要对其中一个设置。配置实例:
每隔5秒执行一次:/5 * ?
每隔1分钟执行一次:0 /1 ?
0 0 10,14,16 ? 每天上午10点,下午2点,4点
0 0/30 9-17 ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
“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 14 * ?” 在每天下午2点到下午2:59期间的每1分钟触发
“0 0/5 14 ?” 在每天下午2点到下午2:55期间的每5分钟触发
“0 0/5 14,18 ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
“0 0-5 14 ?” 在每天下午2点到下午2:05期间的每1分钟触发
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
“0 15 10 15 * ?” 每月15日上午10:15触发
“0 15 10 L * ?” 每月最后一日的上午10:15触发
“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发
但是注意的是注解@Scheduled的cron不能指定年份,cron只能是6位,若指定了年份比如:
@Scheduled(cron = "0/5 0 0 0 0 ? 2019-2020")
public void testScheduled2() throws InterruptedException {
System.out.println(new Date() + "====>2");
Thread.sleep(3000);
}
这时候启动项目会报错:
Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'testScheduled2': Cron expression must consist of 6 fields (found 7 in "0/5 0 0 0 0 ? 2019-2020")
多线程
默认定时任务是单线程的,当然也可以实现多线程执行。例如:
1.首先创建线程池,通过java配置创建线程池
@Configuration
public class ThreadPoolManager {
@Bean(name = "poolExecutor")
public Executor poolExecutor(){
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
//核心线程数量
poolTaskExecutor.setCorePoolSize(10);
//最大线程数量(当任务大于核心线程数量的时候任务进入到队列中,当队列满的时候就会创建新的线程直到达到最大线程数量)
poolTaskExecutor.setMaxPoolSize(200);
//队列大小
poolTaskExecutor.setQueueCapacity(10);
//闲置线程保存时长
poolTaskExecutor.setKeepAliveSeconds(3000);
//当线程达到最大线程数量的时候的对任务的处理方式
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//初始化
poolTaskExecutor.initialize();
return poolTaskExecutor;
}
}
2.创建线程
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println(new Date() + "=== 线程:" + Thread.currentThread().getName());
}
}
3.创建定时任务,开启定时任务和对异步任务的支持
@Component
@EnableScheduling
@EnableAsync
public class AsyncScheduled {
@Resource
private Executor poolExecutor;
@Scheduled(fixedRate = 5000)
@Async
public void testScheduled(){
poolExecutor.execute(new MyThread());
}
}
4.结果如下:
Sat Sep 07 17:57:21 CST 2019=== 线程:poolExecutor-10
Sat Sep 07 17:57:26 CST 2019=== 线程:poolExecutor-1
Sat Sep 07 17:57:31 CST 2019=== 线程:poolExecutor-2
Sat Sep 07 17:57:36 CST 2019=== 线程:poolExecutor-3
Sat Sep 07 17:57:41 CST 2019=== 线程:poolExecutor-4
Sat Sep 07 17:57:46 CST 2019=== 线程:poolExecutor-5
Sat Sep 07 17:57:51 CST 2019=== 线程:poolExecutor-6
Sat Sep 07 17:57:56 CST 2019=== 线程:poolExecutor-7
Sat Sep 07 17:58:01 CST 2019=== 线程:poolExecutor-8
Sat Sep 07 17:58:06 CST 2019=== 线程:poolExecutor-9
Sat Sep 07 17:58:11 CST 2019=== 线程:poolExecutor-10
Sat Sep 07 17:58:16 CST 2019=== 线程:poolExecutor-1
注解@EnableAsync开启对异步任务的支持、注解@Async表明该方法是个异步方法,如果添加在类上表示这个类里的所有方法都是异步的
多线程任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务