Spring定时任务

1、@Scheduled注解方式

使用方式

  • @Scheduled的使用方式十分简单,首先在项目启动类添加注解@EnableScheduled

  • 编写定时任务方法,方法上添加注解@Scheduled

  • 如果有多个定时任务,可以使用异步或者多线程解决。

参数说明

@Scheduled(fixedRate=2000):上一次开始执行时间点后2秒再次执行;单位ms;
@Scheduled(fixedDelay=2000):上一次执行完毕时间点后2秒再次执行;单位ms;
@Scheduled(initialDelay=1000, fixedDelay=2000):第一次延迟1秒执行,然后在上一次执行完毕时间点后2秒再次执行;
@Scheduled(cron="* * * * * ?"):按cron规则执行。

注解方式总结

  • 如果是强调任务间隔的定时任务,建议使用fixedRate和fixedDelay;

  • 如果是强调任务在某时某分某刻执行的定时任务,建议使用cron表达式。

参考代码示例

@Component
public class testTask {
private Logger logger = LoggerFactory.getLogger(testTask.class); @Scheduled(cron = "0/5 * * * * ?")
public void doTask() {
logger.info(Thread.currentThread().getName()+"===task run");
}
}

按条件自动停止任务

@Slf4j
@Component
public class AutoStopTask { @Autowired
private CustomTaskScheduler customTaskScheduler;
private int count; @Scheduled(cron = "*/3 * * * * *")
public void printTask() {
log.info("任务执行次数:{}", count + 1);
count++;
// 执行3次后自动停止
if (count >= 3) {
log.info("任务已执行指定次数,现在自动停止");
boolean cancelled = customTaskScheduler.getScheduledTasks().get(this).cancel(true);
// 停止后再次启动
if (cancelled) {
count = 0;
ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, ReflectionUtils.findMethod(this.getClass(), "printTask"));
customTaskScheduler.schedule(runnable, new CronTrigger("*/3 * * * * *"));
}
}
}
}

Cron表达式参数详解

关于cron表达式的写法

如:cron="* * * * * ?"

按顺序依次为:1~7(最后一位“年”可省略)

序号 含义 设值范围 通配符
1 (0~59) , - * /
2 分钟 (0~59) , - * /
3 小时 (0~23) , - * /
4 (1~31) , - * ? / L W C
5 (1~12) , - * /
6 星期 (1~7 1=星期日 7=星期六) , - * ? / L C #
7 年份(可选) (1970-2099) , - * /

在线Cron表达式生成器:http://cron.qqe2.com/

表达式参数说明
, # 表示一个列表(1,3,5),用逗号拼接具体的时间值

- # 表示一个连续区间(9-12)

* # 代表所有可能的值

? # 仅被用于天和星期两个子表达式,表示不指定值

/ # 用来指定数值的增量(分钟里的“0/15”表示从第0分钟开始,每隔15分钟)

# # 用来指定具体的周数,"#"前面代表星期,"#"后面代表本月第几周
# 比如"2#2"表示本月第二周的星期一,"5#3"表示本月第三周的星期四
# 因此,"5L"这种形式只不过是"#"的特殊形式而已 L # 仅被用于天和星期两个子表达式,它是单词“last”的缩写,“L”表示这个月的最后一日,“6L”表示这个月的倒数第6天 W # 代表着*日(Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最*的一个*日。
# 大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。
# 日域中的 15W 意味着离该月15号的最*一个*日。假如15号是星期六,那么 trigger 会在14号(星期五)触发,因为星期四比星期一离15号更*。 C # 代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。
# 例如5C在“天”字段中就相当于日历5日以后的第一天。1C在“星期”字段中相当于星期日后的第一天。 由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置“?”号
常用表达式参考
"*/5 * * * * ?"       # 每隔5秒执行一次
"0 */1 * * * ?" # 每隔1分钟执行一次
"0 0 23 * * ?" # 每天23点执行一次
"0 0 1 * * ?" # 每天凌晨1点执行一次
"0 0 1 1 * ?" # 每月1号凌晨1点执行一次
"0 0 23 L * ?" # 每月最后一天23点执行一次
"0 0 1 ? * L" # 每周星期天凌晨1点实行一次:
"0 26,29,33 * * * ?" # 在26分、29分、33分执行一次
"0 0 0,3,8,21 * * ?" # 每天的0点、3点、8点、21点执行一次
"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 ? * 6#3" # 每月的第三个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" # 2002年至2005年的每月的最后一个星期五上午10:15触发

2、执行多个定时任务

2.1、通过@Async注解异步调用

在项目启动类添加注解@EnableAsync

注:@Async所修饰的函数不要定义为static类型,这样异步调用不会生效

创建自定义线程池,使用线程池异步执行多个任务

异步调用
@Component
public class Task { @Async(value ="myPoolTaskExecutor")
public void doTaskOne() throws Exception {
// 同上内容,省略
}
@Async(value ="myPoolTaskExecutor")
public void doTaskTwo() throws Exception {
// 同上内容,省略
}
@Async(value ="myPoolTaskExecutor")
public void doTaskThree() throws Exception {
// 同上内容,省略
} /**
* 创建自定义线程池,提供异步调用时使用
**/
@Bean(name = "myPoolTaskExecutor")
public ThreadPoolTaskExecutor getMyPoolTaskExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(10);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(100);
//缓存队列
taskExecutor.setQueueCapacity(50);
//许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(200);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("poolTestThread-");
/**
* 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
*/
// 拒绝策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize(); System.out.println("@Async 业务处理线程配置成功,核心线程池:[{}],最大线程池:[{}],队列容量:[{}],线程名称前缀:[{}]");
return taskExecutor;
}
}
异步回调(扩展知识)
@Async(value ="myPoolTaskExecutor")
public Future<String> doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务一完成");
}
//注意:调用方与被调方不能在同一个类 @Test
public void test() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = task.doTaskOne(); //异步任务1
Future<String> task2 = task.doTaskTwo(); //异步任务2
Future<String> task3 = task.doTaskThree(); //异步任务3
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三个任务都调用完成,处理点其它事,退出循环等待
break;
}
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
@Async不生效的解决方法

1、检查是否配置相关注解

在需要用到 @Async 注解的类上加上 @EnableAsync,或者直接加在springboot启动类上;

2、在同一个类中调用需要先获取代理对象,也就是手动获取对象。

因为 @Async 注解是基于Spring AOP (面向切面编程)的,而AOP的实现是基于动态代理模式实现的。有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。

public class TestController{

    @GetMapping("/testAsync")
public String testAsync() throws InterruptedException {
// 手动获取代理对象
SpringUtil.getBean(TestController.class).syncData();
return "测试异步完成";
} @Async
public void syncData() throws InterruptedException {
System.out.println("异步方法执行......" + Thread.currentThread().getId());
}
}

点击查看手动获取代理对象代码实现

3、不同的类调用,直接注入即可

public class TestController{
@Autowired
private TestService testService; @GetMapping("/testAsync")
public String testAsync() throws InterruptedException {
testService.syncData();
return "测试异步完成";
}
} public class TestServiceImpl{
@Async
public void syncData() throws InterruptedException {
System.out.println("异步方法执行......" + Thread.currentThread().getId());
}
}
@Async产生的异常问题

SpringBoot使用@Async导致的循环依赖报错的解决方案

SpringBoot使用@Async注解的8大陷阱

2.2、通过实现SchedulingConfigurer接口

Spring 中,创建定时任务除了使用@Scheduled 注解外,还可以使用 SchedulingConfigurer。

@Schedule 注解有一个缺点,其定时的时间不能动态的改变,而基于 SchedulingConfigurer 接口的方式可以做到。

点击查看基于Spring的SchedulingConfigurer实现动态定时任务

@Configuration
public class TaskConfig implements SchedulingConfigurer { @Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(10);
executor.setThreadNamePrefix("task-thread");
//设置饱和策略
//CallerRunsPolicy:线程池的饱和策略之一,当线程池使用饱和后,直接使用调用者所在的线程来执行任务;如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
} //配置@Scheduled 定时器所使用的线程池
//配置任务注册器:ScheduledTaskRegistrar 的任务调度器
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
//可配置两种类型:TaskScheduler、ScheduledExecutorService
//scheduledTaskRegistrar.setScheduler(taskScheduler());
//只可配置一种类型:taskScheduler
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
}

编写定时任务

//注册为spring容器的组件
@Component
@Slf4j
public class SchedulerTask { //定时任务
// 5 * * * * ? 在每分钟的5秒执行
@Scheduled(cron = " 5 * * * * ? ")
public void scheduleTask() {
try {
log.info("定时任务: 开始执行");
//todo:执行业务
log.info("定时任务: 执行完毕");
} catch (Exception e) {
log.error("定时任务执行出错", e);
}
}
}

3、Spring线程池和Jdk线程池

  • jdk线程池就是使用jdk线程工具类ThreadPoolExecutor 创建线程池
  • spring线程池就是使用自己配置的线程池,然后交给spring处理,可以采用Async,也可以引入线程池的bean依赖。

记一下线程池优化不当导致的资源耗尽

3.1、JDK普通线程池

由于该接口访问频率很频繁,每次访问会创建新的线程池,另外接口的循环内部不断创建线程处理,导致访问次数多了系统内部资源耗尽。

public void taskTest(){
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch countDownLatch = new CountDownLatch(subjectList.size());
try {
for (Subject subject : subjectList) {
Member finalMember = member;
executorService.execute(() -> {
//耗时操作...
content.add(subjectMap);
countDownLatch.countDown();
});
}
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}

3.2、JDK普通线程池(优化)

不用每次创建新的线程池

public class ThreadPoolTest {
private final Logger logger= LoggerFactory.getLogger(ThreadPoolTests.class);
//JDK普通线程池
private ExecutorService executorService= Executors.newFixedThreadPool(10);
private void sleep(long m){
try {
Thread.sleep(m);
} catch (InterruptedException e) {
e.printStackTrace();
}
} @Test
public void testExecutorService(){
Runnable task=new Runnable() {
@Override
public void run() {
logger.debug("Hello ExecutorService");
}
};
for(int i=0;i<10;i++){
//调用submit方法,线程池会分配一个线程进行执行这个任务
executorService.submit(task);
}
sleep(10000);
}
}

3.3、Spring默认的线程池SimpleAsyncTaskExecutor

Spring异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor

在没有配置的情况下,默认使用的是simpleAsyncTaskExecutor。(Spring提供了7个线程池的实现,感兴趣的可以自行了解)

特点

每次执行任务时,它会重新启动一个新的线程,并允许开发者控制并发线程的最大数量(concurrencyLimit),从而起到一定的资源节流作用。

默认是concurrencyLimit取值为-1,即不启用资源节流。

3.4、Spring自带的线程池ThreadPoolTaskExecutor

ThreadPoolTaskExecutor类,其本质是对java.util.concurrent.ThreadPoolExecutor的包装。这个类则是spring包下的,是Spring为我们开发者提供的线程池类。

Spring提供了xml给我们配置ThreadPoolTaskExecutor线程池,但是现在普遍都在用SpringBoot开发项目,所以直接上yaml或者properties配置即可,或者也可以使用@Configuration配置也行,下面演示配置和使用:

配置:application.properties
# 核心线程池数
spring.task.execution.pool.core-size=5
# 最大线程池数
spring.task.execution.pool.max-size=10
# 任务队列的容量
spring.task.execution.pool.queue-capacity=5
# 非核心线程的存活时间
spring.task.execution.pool.keep-alive=60
# 线程池的前缀名称
spring.task.execution.thread-name-prefix=线程前缀名
配置:application.yaml
spring:
task:
execution:
pool:
#核心线程数
core-size: 5
#最大线程数
max-size: 20
#任务队列容量
queue-capacity: 10
#非核心线程存活时间
keep-alive: 60
#线程池前缀名称
thread-name-prefix: 线程前缀名
配置Config类
@Configuration
public class AsyncScheduledTaskConfig { @Value("${spring.task.execution.pool.core-size}")
private int corePoolSize;
@Value("${spring.task.execution.pool.max-size}")
private int maxPoolSize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queueCapacity;
@Value("${spring.task.execution.thread-name-prefix}")
private String namePrefix;
@Value("${spring.task.execution.pool.keep-alive}")
private int keepAliveSeconds; @Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutorInit() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//最大线程数
executor.setMaxPoolSize(maxPoolSize);
//核心线程数
executor.setCorePoolSize(corePoolSize);
//任务队列的大小
executor.setQueueCapacity(queueCapacity);
//线程前缀名
executor.setThreadNamePrefix(namePrefix);
//线程存活时间
executor.setKeepAliveSeconds(keepAliveSeconds);
/**
* 拒绝处理策略
* CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
* AbortPolicy():直接抛出异常。
* DiscardPolicy():直接丢弃。
* DiscardOldestPolicy():丢弃队列中最老的任务。
*/
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
// executor.setWaitForTasksToCompleteOnShutdown(true);
//线程初始化
executor.initialize();
return executor;
}
}
线程池的使用

需在启动类加上@EnableAsync和@EnableScheduling两个注解

@Component
public class ScheduleTask { @Qualifier("threadPoolTaskExecutorInit")
@Autowired
ThreadPoolTaskExecutor executor; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Async("threadPoolTaskExecutorInit")
@Scheduled(fixedRate = 2000)
public void test1() {
try {
Thread.sleep(6000);
System.out.println("线程池名称:" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
} @Scheduled(cron = "*/1 * * * * ?")
public void test2() throws ExecutionException, InterruptedException {
CompletableFuture<String> childThread = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" exit,time->"+System.currentTimeMillis());
return Thread.currentThread().getName() + "任务结束";
},executor);
System.out.println("child thread result->"+childThread.get());
}
}

Spring定时任务和@Async注解异步调用的更多相关文章

  1. Spring Boot使用@Async实现异步调用

    原文:http://blog.csdn.net/a286352250/article/details/53157822 项目GitHub地址 : https://github.com/FrameRes ...

  2. Spring Boot使用@Async实现异步调用:自定义线程池

    前面的章节中,我们介绍了使用@Async注解来实现异步调用,但是,对于这些异步执行的控制是我们保障自身应用健康的基本技能.本文我们就来学习一下,如果通过自定义线程池的方式来控制异步调用的并发. 定义线 ...

  3. SpringBoot学习笔记(七):SpringBoot使用AOP统一处理请求日志、SpringBoot定时任务@Scheduled、SpringBoot异步调用Async、自定义参数

    SpringBoot使用AOP统一处理请求日志 这里就提到了我们Spring当中的AOP,也就是面向切面编程,今天我们使用AOP去对我们的所有请求进行一个统一处理.首先在pom.xml中引入我们需要的 ...

  4. spring boot中使用@Async实现异步调用任务

    本篇文章主要介绍了spring boot中使用@Async实现异步调用任务,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 什么是“异步调用”? “异步调用”对应的是“同步 ...

  5. spring boot 学习(十一)使用@Async实现异步调用

    使用@Async实现异步调用 什么是”异步调用”与”同步调用” “同步调用”就是程序按照一定的顺序依次执行,,每一行程序代码必须等上一行代码执行完毕才能执行:”异步调用”则是只要上一行代码执行,无需等 ...

  6. Spring @Async实现异步调用示例

    什么是“异步调用”? “异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行:异步调用指程序在顺序执行时,不等待异步调用的语句返回结果 ...

  7. Spring Boot中使用@Async实现异步调用

    在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsyn ...

  8. Spring Boot2.0之@Async实现异步调用

    补充一个知识点: lombok底层原理使用的是: 字节码技术ASM修改字节码文件,生成比如类似于get() set( )方法 一定要在开发工具安装 在编译时候修改字节码文件(底层使用字节码技术),线上 ...

  9. 56. spring boot中使用@Async实现异步调用【从零开始学Spring Boot】

    什么是"异步调用"? "异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执 ...

  10. 注解@Async解决异步调用问题

    序言:Spring中@Async 根据Spring的文档说明,默认采用的是单线程的模式的.所以在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的. 那么当多个任务的执行势必会相互影响. ...

随机推荐

  1. 怎么样给Oracle数据库中的表添加列?

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1 ...

  2. 一键自动化博客发布工具,用过的人都说好(infoq篇)

    infoq的博客发布界面也是非常简洁的.首页就只有基本的标题,内容和封面图片,所以infoq的实现也相对比较简单. 一起来看看吧. 前提条件 前提条件当然是先下载 blog-auto-publishi ...

  3. Linux上执行内存中的脚本和程序

    在Linux中可以不需要有脚本或者二进制程序的文件在文件系统上实际存在,只需要有对应的数据在内存中,就有办法执行这些脚本和程序. 原理其实很简单,Linux里有办法把某块内存映射成文件描述符,对于每一 ...

  4. Pytorch:以单通道(灰度图)加载图片

    以单通道(灰度图)加载图片 如果我们想以单通道加载图片,设置加载数据集时的transform参数如下即可: from torchvision import datasets, transforms t ...

  5. [数字华容道] Html+css+js 实现小游戏

    [数字华容道] Html+css+js 实现小游戏 效果图 代码预览 在线预览地址 代码示例 <!DOCTYPE html> <html> <head> <m ...

  6. ubuntu禁止指定的软件升级

    禁止软件升级 sudo echo 软件包名 hold | sudo dpkg --set-selections 取消软件禁止升级的限制 sudo echo 软件包名 install | sudo dp ...

  7. RBD与Cephfs

    目录 1. RBD 1. RBD特性 2. 创建rbd池并使用 2.1 创建rbd 2.2 创建用户 2.3 下发用户key与ceph.conf 2.4 客户端查看pool 2.5 创建rbd块 2. ...

  8. 一个基于 Spring Dubbo 微服务的快速开发脚手架,新手入门必备!

    Spring-dubbo-skeleton 这是一个基于 Spring Dubbo 的快速开发脚手架,Github 地址:https://github.com/yxhsea/spring-dubbo- ...

  9. Chart.js (v2.9.4) 2-主要的函数和对象介绍

    Color() :主要负责渲染图表时候,针对颜色处理相关函数 helpers_core:工具对象,提供了基础的工具函数功能,遍历数组,扩展对象,合并对象,克隆对象等等. core_defaults:负 ...

  10. Vue cli之传递数据

    1.父组件的数据传递给子组件 // 父组件 <Menu title="来自Home的数据" :clickNum="num"></Menu> ...