文章目录
1. ThreadPoolTaskExecutor是什么
ThreadPoolTaskExecutor是Spring基于java本身的线程池ThreadPoolExecutor做的二次封装,主要目的还是为了更加方便的在spring框架体系中使用线程池。
2. 为什么使用ThreadPoolTaskExecutor
一句话:可以让使用线程池的编码更方便更优雅。
使用线程池时,主要有两种方式:ThreadPoolExecutor和ThreadPoolTaskExecutor;
2.1直接使用ThreadPoolExecutor
原始的使用线程池,要么直接new ThreadPoolExecutor,要么使用jdk中的Executors工具类,此工具类提供了一些常用的api来创建线程池。
但是,都需要在业务代码中显式的编程,代码不够简洁。
2.2 使用ThreadPoolTaskExecutor
目前大家都在使用spring全家桶编写服务代码。也都习惯了使用注解式编程方式。ThreadPoolTaskExecutor本身相当于一个普通的bean,他只是基于ThreadPoolExecutor做了一定的封装,线程池的核心逻辑还是ThreadPoolExecutor来实现的,一些线程池的基本配置参数,也和原始的ThreadPoolExecutor的保持一致。
下面来直接通过使用demo来直观的感受下。
3. 如何使用ThreadPoolTaskExecutor
ThreadPoolTaskExecutor在spring体系中,就是一个普通的bean,所以支持bean的各种配置方式,本文demo中,就直接介绍最常用的java配置类方式。
3.1 配置
直接写一个配置类即可:
@EnableAsync
@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public Executor schedulingTaskExecutor() {
return initExecutor(4, 4, 1, new ThreadPoolExecutor.AbortPolicy(), "taskExecutor-");
}
private Executor initExecutor(int corePoolSize, int maxPoolSize, int queueCapacity, RejectedExecutionHandler rejectedExecutionHandler, String threadNamePrefix) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(60);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setRejectedExecutionHandler(rejectedExecutionHandler);
executor.setTaskDecorator(new ContextCopyingDecorator());
return executor;
}
}
上面我们配置了一个线程池,名字是"taskExecutor";
参数和ThreadPoolExecutor基本一致,其中executor.setTaskDecorator(new ContextCopyingDecorator());可以是非必须的,可以不设置,具体作用在下文中介绍。
另外:@EnableAsync是spring的注解,表示开启异步代理。
当然,如果需要多个线程池,不同的业务使用不同的线程池,那么只需要在这里配置多个线程池即可,名字区分开即可。
3.2 业务中使用
上文配置类中,我们配置了一个线程池,bean的名字是“taskExecutor”,业务中就可以使用此名字进行注入。
@Async("taskExecutor")
@Override
public void testMethod() {
//业务代码
....
//
}
这是一个普通的接口的实现方法;
这里我们配合@Async注解使用,参数就是我们配置的线程池的名字。这样testMethod()在执行的时候,就自动使用了线程池。
在需要使用线程池的地方,只需要在方法上加上@Async(“taskExecutor”)即可。
需要注意的是,异步的方法必须使用接口的方式调用,同一个类的内部方法之间调用会失效。这个道理和spring的事务注解@Transactional一样的,不过多解释,注意到就好。
3.3 TaskDecorator
上文中的配置线程池参数时,有这么一行配置:
executor.setTaskDecorator(new ContextCopyingDecorator());
参数ContextCopyingDecorator是我们自定义的类,是一个任务装饰类,实现了接口TaskDecorator。
什么作用呢?通过一个场景来简单说明:
一个常用的业务场景是我们经常使用ThreadLocal,但是线程池是新开的线程,这就导致ThreadLocal中保存的参数失效,也就是在使用了线程池的代码中无法取到主线程中设置的ThreadLocal参数。
这个时候,就需要对ThreadLocal中参数在多个线程中传递,那么就使用到了TaskDecorator这个参数,如下:
@Slf4j
public class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
//获取主线程中设置的 user对象
Object user = ThreadLocalUtil.get(ThreadLocalUtil.USER_KEY).orElse(null);
return () -> {
try {
//把user对象重新copy传递给子线程
if (user != null) {
ThreadLocalUtil.set(ThreadLocalUtil.USER_KEY, user);
}
runnable.run();
} finally {
ThreadLocalUtil.remove();
}
};
}
}
public class ThreadLocalUtil {
public static final String USER_KEY = "user";
private static final ThreadLocal<Map <String, Object>> THREAD_LOCAL = ThreadLocal.withInitial(() -> new HashMap <>(4));
public static Optional<Object> get(String key) {
return Optional.ofNullable(THREAD_LOCAL.get().get(key));
}
public static void set(String key, Object obj) {
THREAD_LOCAL.get().put(key, obj);
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
通过ContextCopyingDecorator实现了接口TaskDecorator,在Runnable decorate(Runnable runnable)方法中,实现了Threadlocal线程本地变量在线程间的传递。
其中,ContextCopyingDecorator中的finally必须得加,这里是子线程执行完毕后,清理threadlocal,防止内存泄漏。
以上就是ThreadPoolTaskExecutor最常用的使用方式,一个配置类加几个注解就可以方便的使用线程池,是不是感觉很优雅、、