简介
Spring中用@Async注解标注方法成为异步方法。它会在调用方当前线程之外独立一个线程执行,相当于我们直接new一个线程去执行:
new Thread(() -> System.out.println("new: " + Thread.currentThread().getName())).start();
为什么要使用异步执行?
很多时候我们需要调用一个耗时的或者非主链路的方法,它的执行结果并不重要,我们不需要阻塞等待它执行结束,再去执行后续的方法。同步的做法是任务依次执行,效率较低,任务的总时间是每个子任务的耗时总和。
采用异步执行的好处就是提高任务的执行效率,如果不用关心子任务的执行时间,任务的总时间即为主任务的执行时间。
使用
@Async
关注关键点:
- 指出该注解也可以标注类,代表类的所有方法都是异步方法
- 任意参数类型都是支持的,但是方法返回值必须是void或者Future类型。当使用Future时,你可以使用实现了Future接口的ListenableFuture接口或者CompletableFuture类与异步任务做更好的交互。如果异步方法有返回值,没有使用Future类型的话,调用方获取不到返回值。
- @Async可以指定一个特定的BeanName(即线程池,Executor/TaskExecutor)来执行方法,修改在类上表示类的所有方法都是用该bean来执行,方法级别的修饰高于类级别的修饰
- 调用异步方法类上需要配置上注解@EnableAsync
/**
* Annotation that marks a method as a candidate for <i>asynchronous</i> execution.
* Can also be used at the type level, in which case all of the type's methods are
* considered as asynchronous.
*
* <p>In terms of target method signatures, any parameter types are supported.
* However, the return type is constrained to either {@code void} or
* {@link java.util.concurrent.Future}. In the latter case, you may declare the
* more specific {@link org.springframework.util.concurrent.ListenableFuture} or
* {@link java.util.concurrent.CompletableFuture} types which allow for richer
* interaction with the asynchronous task and for immediate composition with
* further processing steps.
*
* <p>A {@code Future} handle returned from the proxy will be an actual asynchronous
* {@code Future} that can be used to track the result of the asynchronous method
* execution. However, since the target method needs to implement the same signature,
* it will have to return a temporary {@code Future} handle that just passes a value
* through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult},
* or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0
* @see AnnotationAsyncExecutionInterceptor
* @see AsyncAnnotationAdvisor
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
/**
* A qualifier value for the specified asynchronous operation(s).
* <p>May be used to determine the target executor to be used when executing this
* method, matching the qualifier value (or the bean name) of a specific
* {@link java.util.concurrent.Executor Executor} or
* {@link org.springframework.core.task.TaskExecutor TaskExecutor}
* bean definition.
* <p>When specified on a class level {@code @Async} annotation, indicates that the
* given executor should be used for all methods within the class. Method level use
* of {@code Async#value} always overrides any value set at the class level.
* @since 3.1.2
*/
String value() default "";
}
基础使用
定义接口
test方法使用@Async注解修饰
public interface AsyncTestService {
@Async
void test();
}
实现接口
方法入口打印线程名,sleep后输出任务结束
@Service
public class AsyncTestServiceImpl implements AsyncTestService {
@Override
public void test() {
System.out.println("测试异步调用,ThreadName:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步调用调用结束!");
}
}
启动类
记录执行时间,并打印线程名,异步调用test方法,主线程继续输出结果,sleep后输出任务结束
@SpringBootApplication
public class SpringdemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringdemoApplication.class, args);
long start = System.currentTimeMillis();
System.out.println("主线程执行,ThreadName:" + Thread.currentThread().getName());
AsyncTestService testService = (AsyncTestService) applicationContext.getBean("asyncTestServiceImpl");
testService.test();
try {
System.out.println("主线程输出:Hello World!");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行完毕,耗时:" + (System.currentTimeMillis() - start));
}
}
测试结果
打印结果如下,分析:主线程执行输出“主线程执行,ThreadName:main”;testService.test()为异步执行,主线程输出“主线程输出:Hello World!”;主线程sleep,子线程异步执行test方法,输出“测试异步调用,ThreadName:task-1”;由于test方法sleep时间较短,输出“异步调用调用结束!”;最后主线程唤醒,输出“主线程执行完毕,耗时:2012”。可以看到test方法是异步执行。
主线程执行,ThreadName:main
主线程输出:Hello World!
测试异步调用,ThreadName:task-1
异步调用调用结束!
主线程执行完毕,耗时:2012
同步对比
将接口中的@Async注解注释掉,相当于同步执行,看运行结果
public interface AsyncTestService {
@Async
void test();
}
主线程执行,ThreadName:main
测试异步调用,ThreadName:main
异步调用调用结束!
主线程输出:Hello World!
主线程执行完毕,耗时:7008
可以看到耗时从2012增加到7008(具体耗时可以多测几次,取平均值,但是趋势是一致的),严重影响了效率。
自定义线程池
定义线程池Bean
@EnableAsync(mode = AdviceMode.PROXY)
@Configuration
public class AsyncThreadPoolConfig {
/**
* 核心线程数(默认线程数)
*/
private static final int CORE_POOL_SIZE = 8;
/**
* 最大线程数
*/
private static final int MAX_POOL_SIZE = 20;
/**
* 允许线程空闲时间(单位:默认为秒)
*/
private static final int KEEP_ALIVE_TIME = 10;
/**
* 缓冲队列大小
*/
private static final int QUEUE_CAPACITY = 200;
/**
* 线程池名前缀
*/
private static final String THREAD_NAME_PREFIX = "async-task-pool-";
/**
* 当使用@Async注解时,需指定使用此线程池
*
* @return 线程池实例
*/
@Bean("asyncTaskExecutor")
public ThreadPoolTaskExecutor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.setThreadFactory(new ThreadFactory() {
// 线程计数器
private final AtomicInteger threadNumber = new AtomicInteger(0);
@Override
public Thread newThread(@NotNull Runnable runnable) {
Thread thread = new Thread(runnable, THREAD_NAME_PREFIX + threadNumber.getAndIncrement());
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY);
}
return thread;
}
});
// 线程池对拒绝任务的处理策略,拒绝执行且抛出异常
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 初始化
executor.initialize();
return executor;
}
}
定义接口
指定@Aysnc的value为线程池的beanName
public interface AsyncTestService {
@Async(value = "asyncTaskExecutor")
void test();
}
测试结果
实现累、启动类同上,测试结果如下,可以看到异步线程名改为了所配置的规则
主线程执行,ThreadName:main
主线程输出:Hello World!
测试异步调用,ThreadName:async-task-pool-0
主线程执行完毕,耗时:2022
异步调用调用结束!
对比
在自定义线程池的场景下,相比基础使用我们可以做许多配置,常用配置如下:
- 自定义核心线程数
- CPU密集型:N + 1
- IO密集型:2 * N + 1
- 自定义线程池缓冲队列大小
- 如果该场景下任务处理比较频繁,就需要扩大缓冲区大小,反之则缩小
- 选择合适的饱和策略
-
AbortPolicy(默认策略):抛出RejectedExecutionException来拒绝新任务的处理
-
DiscardOldestPolicy:丢弃最早的未处理的任务请求
-
DiscardPolicy:直接丢弃任务,不执行
-
CallerRunsPolicy:在调用execute的线程里面执行此command,会阻塞入口
线程池执行策略
- 任务过来,如果当前工作线程数小于核心线程数corePoolSize,则创建一个新线程执行任务
- 如果当前工作线程数等于大于核心线程数,则将任务加入到阻塞队列中BlockingQueue
- 如果无法将任务加入阻塞队列(队列已满),则创建非corePool线程来处理任务
- 如果创建非corePool线程使得线程池线程数量大于最大线程数maxmumPoolSize,则执行饱和策略的RejectedExecutionHandler.rejectedExecution()