一、Spring框架为我们提供了基于线程池的异步调用支持,用法也很简单。
特别注意:通常调用方法写在contorller类中,而异步执行业务逻辑放在service类中
。
1.controller方法本身就在servlet容器的线程池中同步执行。
2.若controller方法被标记为异步执行,则这个方法会被提交到非servlet容器线程池。
3.若controller方法为同步执行,而被调用方法又需要异步执行,则被调用方法必须放在另一个类中(如service类),异步方法才会被提交到非servlet容器线程池,否则,被调用方法仍然在servlet容器线程池中与controller方法一起同步执行,违背目的。
更正补充:异步失效的原因,由于调用方法和被调用方法在同一个对象所导致,具体细节可搜索【@Transactional和@Async注解失效】,由于spring中的事务注解和异步注解提供的增强逻辑都是基于动态代理实现的,要想让增强逻辑生效,被调用方法必须来自代理对象。
从代理对象这个点出发,解决上述注解失效的方法:
1. 将被调用方法单独放在一个类里,由spring容器构造代理bean,然后注入使用。
2. 不单独出类,在本类中依赖注入本类的bean(这个bean由spring容器托管,实为代理类的bean),作为成员变量,被调用方法由这个成员变量的bean提供。
3. 显式使用cglib,从AopContext.currentProxy()获取代理对象。
二、使用默认线程池
如果用户未声明异步线程池,spring将开启一个默认线程池来处理异步调用。
- 用@EnableAsync注解开启应用的异步调用功能。
@EnableAsync
@Configuration
public class AsyncConfig {
}
- 在controller类中给需要异步执行的方法上打上@Async注解,告诉spring此方法需要异步执行(此方法必须可被Overied)。
/**
* @author: Yang
* @date: 2020/4/6 15:09
* @description:
*/
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncController {
@Resource
private AsyncService asyncService;
/**
* 无调用关系,无返回值的异步方法
*/
@Async
@GetMapping("/sameClass")
public void sameClass() {
log.error(Thread.currentThread().getName());
}
/**
* 有调用关系,无返回值的异步调用
*/
@GetMapping("/noResult")
public void noResult() {
this.asyncService.no();
}
/**
* 有调用关系,有返回值的异步调用
*
* @return
*/
@GetMapping("/haveResult")
public Long haveResult() {
Long kk = null;
Future<Long> have = this.asyncService.have();
if (have.isDone()) {
try {
kk = have.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
Thread.interrupted();
}
}
return kk;
}
}
- 在service类中给需要异步执行的方法上打上@Async注解,告诉spring此方法需要异步执行(此方法必须可被Overied)。
@Slf4j
@Component
class AsyncService {
@Async
protected void no() {
log.error(Thread.currentThread().getName());
}
@Async
protected Future<Long> have() {
log.error(Thread.currentThread().getName());
log.error("---------{}", Thread.currentThread().getId());
return new AsyncResult<>(Thread.currentThread().getId());
}
}
- 分别在浏览器访问几次下列接口进行测试:
http://localhost:8081/async/sameClass //控制台打印出线程name【task-1、task-2、task-3】
http://localhost:8081/async/noResult //控制台打印出线程name【task-1、task-2、task-3】
http://localhost:8081/async/haveResult //能够得到池中线程id【45,12,44】
注:servlet容器线程池的线程名类似【http-nio-8081-exec-1】
三、使用自定义线程池
- 默认线程池的方式使用的是容器的线程池,不能灵活的控制和业务隔离,建议实践中自定义线程池来处理。
- 只需另行申明一个自定义线程池,配置类实现AsyncConfigurer接口,异步执行的方法声明无区别。
/**
* @author: Yang
* @date: 2020/4/6 14:04
* @description:
*/
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
private static final int MAX_SIZE = 16;
private static final int QUEUE_SIZE = 16;
private static final int KEEP_ALIVE_SECOND = 600;
private static final int AWAIT_TERMINAL_SECOND = 60;
private static final int CORE_SIZE = Runtime.getRuntime().availableProcessors() * 2;
@Bean
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setMaxPoolSize(MAX_SIZE);
pool.setCorePoolSize(CORE_SIZE);
pool.setQueueCapacity(QUEUE_SIZE);
pool.setKeepAliveSeconds(KEEP_ALIVE_SECOND);
pool.setAllowCoreThreadTimeOut(false);
/*
*线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
*表示当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行这个任务;
*如果执行程序已关闭,则会丢弃该任务
*/
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
pool.setThreadNamePrefix("async-");
// 该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.setAwaitTerminationSeconds(AWAIT_TERMINAL_SECOND);
//初始化线程池
pool.initialize();
return pool;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
- 分别在浏览器访问几次下列接口进行测试:
http://localhost:8081/async/sameClass //控制台打印出线程name【async----1、async----2、async----3】
http://localhost:8081/async/noResult //控制台打印出线程name【async----1、async----2、async----3】
http://localhost:8081/async/haveResult //能够得到池中线程id【45,12,44】
四、多个异步线程池
- 有时候一个应用中需要异步调用的业务模块不止一个,建议各业务模块的异步线程池分开配置,异步执行方法分开提交,方便管理和错误排查。
- 开启和配置方式都差不多,只是需要在各个自定义线程池上声明池的名称,异步提交执行的方法上也带上对应的池名称。
@Override
@Bean("myAsyncPool")
public Executor getAsyncExecutor() {
//自定义线程池声明,略
}
@Async("myAsyncPool")
protected void no() {
//异步执行方法声明,略
}