提示: 对 Java 线程池 ThreadPoolExecutor 有所了解的道友阅读本篇更容易。
目录
一、 案例展示
二、 @EnableAsync 和 @Async
三、 查看源码中的线程池默认配置
一、 案例展示
在项目的实际应用中,很多时候一个请求都是在同一个线程中运行的。但有时候可能需要异步,也就是一个请求可能存在多个线程。
在Spring中, 存在一个接口叫做 AsyncConfigurer, 这是一个可配置异步线程池的接口:
public interface AsyncConfigurer {
@Nullable
default Executor getAsyncExecutor() {
return null;
}
@Nullable
default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
它有两个方法, getAsyncExecutor() 和 getAsyncUncaughtExceptionHandler() 。
1. getAsyncExecutor() 方法返回一个自定义的线程池, 开启异步时, 线程池就会提供空闲的线程来执行异步任务;
2. getAsyncUncaughtExceptionHandler(), 因为线程中的业务逻辑可能会抛出异常, 所以这个方法用来定义一个处理异常的处理器;
在实际使用中, 我们只需要写一个自己的类,实现 AsyncConfigurer 接口, 重写这两个方法就好了。
Demo:
1. 我们定义自己的异步线程池配置类 AsyncConfig , 实现 AsyncConfigurer 接口,重写这两个方法。
2. 在 getAsyncExecutor() 方法中, 定义想要用到的线程池类型和参数,此处我们展示的是ThreadPoolTaskExecutor, 这也是最常用的线程池类型;
3. 在 getAsyncUncaughtExceptionHandler() 方法中, 返回自己定义的异常处理器;
4. 该类上面添加 @EnableAsync 注解, 这样Spring就会开启异步可用;
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
/**自定义的异常处理器*/
@Autowired
private AsyncExceptionHandler asyncExceptionHandler;
@Override
public Executor getAsyncExecutor() {
// 定义线程池
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 设置核心线程数
taskExecutor.setCorePoolSize(5);
// 设置最大线程数
taskExecutor.setMaxPoolSize(10);
// 设置队列的大小
taskExecutor.setQueueCapacity(2000);
// 初始化线程池
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return asyncExceptionHandler;
}
}
自定义的异常处理类Demo:
1. 需要实现 AsyncUncaughtExceptionHandler 接口, 重写里面的 handleUncaughtException() 方法;
2. 根据实际项目需要, 我们可以在里面处理异常, 比如例子中为该方法添加了事务的支持, 并将错误信息放入log;
@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);
@Override
@Transactional(rollbackFor = Exception.class)
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
logger.info("method:{}, params:{}, exception:{}", method.getName(), objects, throwable.getMessage());
}
}
下面让我们看看如何在项目中运用它:
定义Service接口,并实现其中的 testAsync 方法:
1. 被@Async标注的方法,称为异步方法,会在独立的线程中被执行。调用它的线程,无需等待它结束,会继续进行别的操作;
public interface AsyncService {
/**
* 测试异步线程池
*/
void testAsync() throws InterruptedException;
}
@Service
public class AsyncServiceImpl implements AsyncService {
private final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);
/**
* 使用注解 @Async, 声明使用异步调用
*/
@Override
@Async
public void testAsync() throws InterruptedException{
Thread.sleep(3000);
logger.info("Service中线程名称:{}", Thread.currentThread().getName());
int i = 1/0;
}
}
在Controller中调用它:
@RestController(value = "/async")
public class AsyncController {
private final Logger logger = LoggerFactory.getLogger(AsyncController.class);
@Autowired
private AsyncService asyncService;
@GetMapping(value = "/test")
public String testAsync() throws InterruptedException {
logger.info("Controller中线程名称:{}", Thread.currentThread().getName());
//异步调用
asyncService.testAsync();
logger.info("Service中延迟了3S, 谁先打印出来呢");
return "hello world";
}
}
2020-06-08 15:41:01,372 INFO (AsyncController.java:23)- Controller中线程名称:http-nio-8888-exec-3
2020-06-08 15:41:01,373 INFO (AsyncController.java:25)- Service中延迟了3S, 谁先打印出来呢
2020-06-08 15:41:04,375 INFO (AsyncServiceImpl.java:24)- Service中线程名称:ThreadPoolTaskExecutor-2
2020-06-08 15:41:04,378 INFO (AsyncExceptionHandler.java:20)- method:testAsync, params:[], exception:/ by zero
我们可以看到:
1. 虽然 "Service中延迟了3S, 谁先打印出来呢" 这句话在 asyncService.testAsync(); 之后执行, 但仍然先打印出来了。 因为这里已经是异步调用了;
2. Controller 和 Service 中打印出来的线程名称, 已经不是同一个线程了;
3. 我们在 Service 中写了1/0 这样会导致异常的代码,也被我们定义的异常处理器成功捕获,并按照我们想要的格式出现在了log中;
二、 @EnableAsync 和 @Async
@EnableAsync:
1. Spring 中有很多 @EnableXX + @XX 的注解, 用于实现对某功能的支持。 比如 @EnableCaching+@Cache 提供缓存功能, @EnableScheduling+@Scheduled 提供定时功能。 同样, 这里的 @EnableAsync 用于开启异步功能, @Async用于表示具体哪些方法支持异步调用。
2. @EnableAsync, 这个注解是加在类上的。 应用中,我们可以把它放在SpringBoot的启动类上,表示我们的项目开启异步;
@Async:
1. @Async, 这个注解既可以加在类上,也可以加载方法上。 加在类上表明这个类中的所有方法都支持异步调用, 加在方法上表明这单个方法支持异步调用;
2. 在上面的demo中, 被 @Async 修饰的方法是没有返回值的, 在实际应用中,若想使用带返回值的方法, 只需稍稍修改一下便好, 比如:
@Override
@Async
public Future<String> testAsync() throws InterruptedException{
Thread.sleep(3000);
logger.info("Service中线程名称:{}", Thread.currentThread().getName());
return new AsyncResult<String>("abc");
}
在方法定义中,
2.1 使用 Future<T> 作为返回值 , 这个就是java concurrent 包下的Future接口 。
2.2 return 中返回一个 AsyncResult<T>, AsyncResult是Spring中的类, 它实现了ListenableFuture<V>这个接口,而ListenableFuture<V>这个接口也是继承了 Future。 源码如下
public class AsyncResult<V> implements ListenableFuture<V>
public interface ListenableFuture<T> extends Future<T>
2.3 然后在调用该方法的代码中,写上:
Future<String> future = asyncService.testAsync();
.....
future.get()
.....
不过future.get() 方法会阻塞主线程, 让本来的异步处理又回到了同步状态。
三、 查看源码中的线程池默认配置
熟悉Java 中 ThreadPoolExecutor 的道友知道, 定义一个线程池,需要的参数可不止这几个, 我们看一下JDK源码中ThreadPoolExecutor的构造函数:
ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 线程经过多久空闲时间会被回收
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工程
RejectedExecutionHandler handler) { // 拒绝策略
可以发现这里有7个参数, 而我们在上面的Demo中只定义了其中几个, 那Spring肯定是用来默认参数进行填充的,再来看一下我们上述自定义的线程池代码:
@Override
public Executor getAsyncExecutor() {
// 定义线程池
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 设置核心线程数
taskExecutor.setCorePoolSize(5);
// 设置最大线程数
taskExecutor.setMaxPoolSize(10);
// 设置队列的大小
taskExecutor.setQueueCapacity(2000);
// 初始化线程池
taskExecutor.initialize();
return taskExecutor;
}
其中有个方法叫: initialize()。 我们点击去看一下, 会进入到一个叫 ExecutorConfigurationSupport 的类中,而我们例子中使用的 ThreadPoolTaskExecutor 正是这个类的子类。
在ExecutorConfigurationSupport 类中有如下定义:
private ThreadFactory threadFactory = this;
private RejectedExecutionHandler rejectedExecutionHandler = new AbortPolicy();
public void initialize() {
if (this.logger.isInfoEnabled()) {
this.logger.info("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (!this.threadNamePrefixSet && this.beanName != null) {
this.setThreadNamePrefix(this.beanName + "-");
}
this.executor = this.initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
}
可以看到如果我们不设置自己的 threadFactory 和 rejectedExecutionHandler, 便会用默认的进行填充。可以看到这里的拒绝策略默认为 AbortPolicy(). 也就是一旦任务队列满了之后,新的任务会被直接丢弃, 并抛出 RejectedExecutionException。 注: Java 中自己创建线程池,默认的拒绝策略也是这个。
接着这里的 initialize() 方法会接着调用 initializeExecutor() 方法继续进行初始化操作, 那我们就接着扒拉下去,看看这个方法:
private int corePoolSize = 1;
private int maxPoolSize = 2147483647;
private int keepAliveSeconds = 60;
private int queueCapacity = 2147483647;
protected ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue<Runnable> queue = this.createQueue(this.queueCapacity);
ThreadPoolExecutor executor;
if (this.taskDecorator != null) {
executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) {
public void execute(Runnable command) {
Runnable decorated = ThreadPoolTaskExecutor.this.taskDecorator.decorate(command);
if (decorated != command) {
ThreadPoolTaskExecutor.this.decoratedTaskMap.put(decorated, command);
}
super.execute(decorated);
}
};
} else {
executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler);
}
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
this.threadPoolExecutor = executor;
return executor;
}
protected BlockingQueue<Runnable> createQueue(int queueCapacity) {
return (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
}
通过上述源码,我们可以进一步发现:
1. 如果queueCapacity默认大小是2147483647, 即Integer.MAX 值。 如果我们自己定义的这个值大于0, 返回的队列实际是一个LinkedBlockingQueue;
2. 默认corePoolSize 是1, maxPoolSize也是2147483647 , 和queueCapacity默认大小是一样的。 试想一下若是我们不自己定义这个maxPoolSize, 真若是同时来了很多task, 便会创建大量的线程, 会不会直接搞挂我们的服务呢,,,有待测试。
3. 默认线程空闲时间超过60秒后, 会被回收;