# 异步线程池

# 配置

在核心模块zc-web-core-spring-boot-starter中,已经配置了@EnableAsync,如果需要配置多个线程池用于不同的任务时,示例代码如下:

/**
 * 线程池配置
 *
 * @author :    quansheng.zhang
 * @date :    2019/7/28 21:31
 */
@Configuration
public class ExecutorConfig {

    @Bean("kafkaTaskExecutor")
    @ConfigurationProperties("zc.executor")
    public Executor kafkaTaskExecutor() {
        return ExecutorMdcTaskBuilder.create().build();
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

其中ExecutorMdcTaskBuilder (opens new window)since4.3.0,作用:ThreadPoolTaskExecutor建造者,打印MDC路径的线程池任务的建造者。

属性配置

# 核心线程数:线程池创建时候初始化的线程数
zc.executor.core-pool-size=10
# 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
zc.executor.max-pool-size=20
#  缓冲队列:用来缓冲执行任务的队列
zc.executor.queue-capacity=2000
# 允许线程的空闲时间(秒):当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
zc.executor.keep-alive-seconds=60
# 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
zc.executor.await-termination-seconds=10
# 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
zc.executor.thread-name-prefix=default-executor-
1
2
3
4
5
6
7
8
9
10
11
12

线程拒绝策略:不在新线程中执行任务,而是有调用者所在的线程来执行 ThreadPoolExecutor.CallerRunsPolicy

# 使用

# @Async

    @Async("kafkaTaskExecutor")
    @Override
    public void asyncToutiao() {
        // 异步线程 日志打印
        log.info("异步线程 日志打印开始");
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("10s后,异步异常,日志打印结束");
    }
1
2
3
4
5
6
7
8
9
10
11
12

特别提示

@Async使用注意事项

  • 异步方法和调用异步方法的方法不能再同一个类
  • 方法所属的类的对象需要是被Spring容器所管理的,也就是指被@Controller @Service @Repository @Component这些注解的类

# ExecutorService

提供以下几种方法将任务提交到ExecutorService对应的线程池:

// Executor 接口的方法
void execute(Runnable command);

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
        long timeout, TimeUnit unit)
        throws InterruptedException;

<T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
        long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

常见面试题:`execute` 和 `submit` 的区别

  1. executeExecutor接口的方法,而submitExecutorService的方法,并且ExecutorService接口继承了Executor接口。
  2. execute只接受Runnable参数,没有返回值;而submit可以接受Runnable参数和Callable参数,并且返回了Future对象,可以进行任务取消、获取任务结果、判断任务是否执行完毕/取消等操作。其中,submit会对RunnableCallable入参封装成RunnableFuture对象,调用execute方法并返回。
  3. 通过execute方法提交的任务如果出现异常则直接抛出原异常,是在线程池中的线程中;而submit方法是捕获了异常的,只有当调用Futureget方法时,才会抛出ExecutionException异常,且是在调用get方法的线程。

# execute(Runnable command)

executorService.execute(new Runnable() {
    public void run() {
       // todo something
    }
});
    
//executorService.shutdown();
1
2
3
4
5
6
7

# submit(Runnable)

Future future = executorService.submit(new Runnable() {
    public void run() {
        // todo something
    }
});
1
2
3
4
5

# submit(Callable)

Callablecall()方法可以返回结果

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        // todo something
        return "Callable Result";
    }
});
1
2
3
4
5
6
Last Updated: a year ago