Async与Scheduled注解

Async与Scheduled

Posted by John Doe on 2022-04-02
Words 893 and Reading Time 3 Minutes
Viewed Times

背景

最近项目中使用了@Async与@Scheduled自定义线程池注解实现定时任务,特此记录实现及其原理。

@Async

简介

@Async是Spring 3.0之后提供的注解,使用@Async注解可以轻松的实现异步调用。

官方文档: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html

异步原理

@Async在默认情况下使用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池。使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误

关系图

@Async配置默认线程池

  • 要配置默认的线程池,要实现AsyncConfigurer类的两个方法
  • 不需要打印运行状况的可以使用ThreadPoolTaskExecutor类构建线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Slf4j
@EnableAsync
@Configuration
public class AsyncThreadConfig implements AsyncConfigurer {
/**
* 定义@Async默认的线程池
* ThreadPoolTaskExecutor不是完全被IOC容器管理的bean,可以在方法上加上@Bean注解交给容器管理,这样可以将taskExecutor.initialize()方法调用去掉,容器会自动调用
*
* @return
*/
@Override
public Executor getAsyncExecutor() {
int processors = Runtime.getRuntime().availableProcessors();
//常用的执行器
//ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//可以查看线程池参数的自定义执行器
ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(1);
taskExecutor.setMaxPoolSize(2);
//线程队列最大线程数,默认:50
taskExecutor.setQueueCapacity(50);
//线程名称前缀
taskExecutor.setThreadNamePrefix("default-ljw-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化(重要)
taskExecutor.initialize();
return taskExecutor;
}

/**
* 异步方法执行的过程中抛出的异常捕获
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
log.error("线程池执行任务发送未知错误,执行方法:{}", method.getName(), ex.getMessage());
}

}

使用

1
2
3
4
5
6
7
8
9
10
11
   
/**
* 使用默认线程池
*/
@Async
public void updateData() {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
log.info("使用默认线程池,耗时:" + (end - start) + "毫秒");
}

@Async使用指定线程池

  • 由于业务需要,根据业务不同需要不同的线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Configuration
public class AsyncThreadConfig implements AsyncConfigurer{

/** size */
private static final Integer SIZE = 4;

/** qsize */
private static final Integer QSIZE = 10;

/** sec */
private static final Integer SEC = 60;

/**
* @description 自定义任务线程池.
*/
@Bean
public Executor myAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//最大线程数
executor.setMaxPoolSize(SIZE);
//核心线程数
executor.setCorePoolSize(SIZE);
//任务队列的大小
executor.setQueueCapacity(QSIZE);
//线程前缀名
executor.setThreadNamePrefix("async-thread-");
//线程存活时间
executor.setKeepAliveSeconds(SEC);

/**
* 拒绝处理策略
* CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
* AbortPolicy():直接抛出异常。
* DiscardPolicy():直接丢弃。
* DiscardOldestPolicy():丢弃队列中最老的任务。
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
//线程初始化
executor.initialize();
return executor;
}

}

使用:

1
2
3
4
5
6
7
8
9
10
   /**
* 使用默认线程池
*/
@Async("myAsync")
public void updateData() {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
log.info("使用默认线程池,耗时:" + (end - start) + "毫秒");
}

@Scheduled

简介

Spring提供的用于定时任务的注解。

单线程原理

@Scheduled未指定线程池默认使用默认单线程(java自己实现的线程池newSingleThreadScheduledExecutor)去执行定时任务,当项目中定时器多起来,这是该线程如果执行别的定时任务阻塞,则会导致其余的定时任务执行时间间隔变长,未按指定延迟时间执行定时任务,可以使用@Async解决。

源码:

1
2
3
4
5
6
7
8
9
10
11
12
private ScheduledExecutorService initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
if (scheduledExecutor != null) {
this.scheduledExecutor = scheduledExecutor;
this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&
managedScheduledExecutorServiceClass.isInstance(scheduledExecutor));
}
else {
this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
this.enterpriseConcurrentScheduler = false;
}
return this.scheduledExecutor;
}

@Scheduled使用

1
2
3
4
5
@Async("myAsync")
@Scheduled(cron = "0 0/3 * * * ?")
public void updateData() {
dataService.updateData();
}

注:启动类需添加注解@EnableScheduling


This is copyright.

...

...

00:00
00:00