很早之前在项目中就应用了异步,但是一直没有系统整理,今天来整理分享一下。
异步编程允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,使用异步编程可以大大提高我们程序的吞吐量,可以更好的面对更高的并发场景并更好的利用现有的系统资源,提升用户体验感。 所以合理在项目中运用异步编程是有必要的。
Java实现异步编程
new Thread
这是java中实现异步编程最简洁的方式,创建一个新线程
new Thread(()->{
// 要处理的任务
}).start();
存在问题
- 创建线程没有复用,频繁的线程创建和销毁浪费开销
- 没有限制线程个数,可能把系统线程用尽,引发严重事故
所以这种方式只能测试时使用,不可以应用在生产环境中
线程池
Executors可以创建四种常用的线程池
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要可灵活回收空闲线程,若无可回收,则新建线程。不设上限,提交的任务将立即执行。newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor:创建一个单线程化的线程池执行任务。
创建完线程池后,我们就可以调用submit()方法向线程池里提交任务
Spring中的@Async
Spring开始提供了@Async注解用于异步方法调用,注解可以被标注在方法上,以便异步调用该方法,方法的实际会提交给Spring TaskExecutor,由指定线程池中的线程执行
先要在项目中添加@EnableAsync开启异步任务支持
- @Async默认异步配置使用的是SimpleAsyncTaskExecutor,默认来一个任务创建一个线程,不算严格意义上的线程池,达不到线程复用的效果
- 实际应用中推荐自定义@Async的线程池
自定义线程池
Spring中提供了多种线程池
- SimpleAsyncTaskExecutor
- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。
- ThreadPoolTaskScheduler:支持定时任务
- ThreadPoolTaskExecutor:本质是对java.util.concurrent.ThreadPoolExecutor的包装
配置默认线程池
只需要定义一个配置类并实现AsyncConfigurer接口,重写getAsyncExecutor(),指定默认线程池
@Configuration
@EnableAsync
public class AsynConfig implements AsyncConfigurer {
/**
* 异步任务执行线程池
* @return
*/
@Bean(name = "asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(600);
executor.setMaxPoolSize(20);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Override
public Executor getAsyncExecutor() {
return asyncExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects);
};
}
为@Async指定线程池名字
项目中我们可以根据不同业务的特点定义不同的线程池,可以通过为@Async指定线程池名字的方式,为它指定不同的线程池,当然,如果不指定名字,用的就是默认的了
@Async("taskExecutor1")
public void task1() {
}
@Async("taskExecutor2")
public void task2() {
}
常见问题
@Async看起来很方便,但是很容易使用不当造成失效,下面是几个常见的失效场景
未使用@EnableAsync
使用@Async之前需要使用该注解开启Spring异步任务支持
内部方法调用
我们在日常开发中,经常需要在一个方法中调用另外一个方法,例如:
@Slf4j
@Service
public class UserService {
public void test() {
async("test");
}
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
Spring通过@Async注解实现异步的功能,底层其实是通过Spring的AOP实现的,也就是说它需要通过JDK动态代理或者cglib,生成代理对象。
向上面这样在一个类中直接调用相当于调用了this.async()方法,根本没交给Spring处理,异步功能失效
方法返回值错误
在AsyncExecutionInterceptor类的invoke()方法,会调用它的父类AsyncExecutionAspectSupport中的doSubmit方法,该方法时异步功能的核心代码,如下:
@Nullable
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return CompletableFuture.supplyAsync(() -> {
try {
return task.call();
} catch (Throwable var2) {
throw new CompletionException(var2);
}
}, executor);
} else if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor)executor).submitListenable(task);
} else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
} else {
executor.submit(task);
return null;
}
}
可以看出方法的返回值要么是null,要么是Future,因此在实际项目中,要想使用@Async相关方法返回值必须是void或者Future
方法用static修饰
因为这种情况idea会直接报错:Methods annotated with '@Async' must be overridable
使用@Async注解声明的方法,必须是能被重写的。