【桦说并发下篇】漫谈线程池
禁止转载。
本文提及的线程池可以指代具体的线程池或者线程池提供的服务(ExecutorService)。
理想的线程池
- 作为任务执行的载体,两者解耦,任务无需关注自己是由哪个线程执行的。
- Bulkhead 模式,根据任务类型的不同(IO 密集型任务、CPU 密集型任务、延迟任务、执行多次的定时任务、限制超时时间的任务、多种任务组合的任务等),应该由不同的线程池执行。使用时可以对线程池进行封装。
- 作为一种有限资源,线程支持复用和回收,理想情况是恰好满足任务需要,极端情况下避免资源耗尽。
- 可动态配置,可监控。
- 任务执行支持回调,任务可编排,可取消。
Bulkhead 模式
中文名为舱壁模式,bulkhead 指的是划分船舱的舱壁,使用舱壁可以保证当一部分船舱漏水时,船只整体不至于下沉。适用于系统容错设计,不让一小部分模块或组件的失效影响整体的可用性。根据任务的不同,不同的线程池应该隔离使用,使用线程池是实现 Bulkhead 模式的常用方案。Resilience4J 提供了相关支持。
线程池作为上下文
// 任务执行和线程池解耦的伪代码表示
CompletableFuture<List<User>> users;
// IO密集型任务
IO_CONTEXT {
var u1 = getUser("a");
var u2 = getUser("b");
var u3 = getUser("c");
// CPU 密集型任务
CPU_CONTEXT {
users = report(u1, u2, u3);
}
}
return users.join();
实现中的问题
1. 上下文传递
虽然基于 ThreadLocal 的上下文传递并不是编码的最佳实践,但是现状是很多框架、监控系统使用了 ThreadLocal 传递上下文,可以实现无侵入性的代码功能增强。Java 官方并没有提供线程池 ThreadLocal 方案。TransmittableThreadLocal(TTL)支持了线程池传递上下文的问题。
2. ExecutorService 和 ScheduledExecutorService
标准库提供的接口,可拓展。
提供了异步执行方法,submit 返回 Future。但是 submitAny 和 submitAll 均为阻塞方法。
ScheduledExecutorService 提供定时任务执行方法:schedule(任务执行一次),scheduleAtFixedRate(任务执行多次),scheduleWithFixedDelay(任务执行多次),对返回结果 Future 使用 cancel 可以取消任务。
最大的问题是返回的 Future 对象不支持回调,直接使用只能调用阻塞方法 get。
3. ThreadPoolExecutor 和 ScheduledThreadPoolExecutor
可以满足绝大多数任务的需要,支持配置不同的线程池参数:核心线程、临时线程、任务队列、线程工厂、拒绝策略等。
支持回调函数:beforeExecute, afterExecute, terminate。
具体执行流程、状态变化、优雅关闭等内容不再赘述,这里仅说明其常用配置方法:
- 阻塞队列的选择:阻塞队列按照长度划分为 SynchronousQueue(0)、ArrayBlockingQueue(有限值)、LinkedBlockingDeque(有限值或无限)。SynchronousQueue 适用于数据直接传递,其他的 blockingQueue 保证任务进入队列,待到特定条件执行。DelayedWorkQueue 底层使用二叉堆,适用于优先级(延迟时间)排序。
- IO 密集型任务:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
以上工具类方法适用于短期大量请求,但是可能造成大量线程同时段创建与销毁,解决方案是
1. 指定最大线程数(足够大,同时系统又不至于崩溃)和拒绝策略,设置好兜底策略。可以自行封装成 SafeExecutors 工具类或者使用相关类库如 Resilience4J Bulkhead。
1. 限流
1. 流量监控+告警
总之,IO 密集型任务不会占用多少 CPU,不必追求 CPU 占用率而提高线程数。临时线程数应该满足短期内任务的需要。
- CPU 密集型任务
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
以上工具类方法可以接受无限的任务,可能会造成任务延后执行并失效,应该设置为满足业务相关的队列大小。解决方法为:
1. 指定队列大小和拒绝策略,设置好兜底策略。可以自行封装成 SafeExecutors 工具类或者使用相关类库如 Resilience4J Bulkhead。
1. 限流
1. 流量监控+告警
- 混合型任务
如拉取消息队列数据后,进行聚合处理。一般可以处理为多个线程池,各司其职。
- 单线程线程池
和直接 new Thread() 结果一致,但是可以方便监控,避免错误使用,多个任务依次执行,一定程度上避免并发问题。
任务包装逻辑
这里不讨论具体的线程执行任务流程,仅讨论 AbstractExecutorService 对任务的封装。
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
// 略去其他
}
从代码中可得知,submit 内部调用了 execute 方法,后续子类只需要实现 execute 方法即可。FutureTask 实际上是 ThreadPoolExectutor 返回 Future 的具体实现。FutureTask 实现了 Runable、Future 接口,同时支持结果的同步。
4. ForkJoinPool
本意是基于工作窃取算法,支持可递归任务,提高并发效率,但是被严重滥用。
ForkJoinPool 类提供了 commonPool 方法,可以获取全局公用线程池,无法手动关闭。Stream 流式编程和 CompletableFuture 默认使用了全局公用的线程池,违反了 Bulkhead 模式,不建议使用。尽管 ForkJoinPool 尽力保证不会出现不活跃线程,如果部分任务执行出现错误,仍然会影响其他任务的执行。而且出现错误的概率比单线程编程高很多,可能出现的问题不限于:提交了阻塞 IO 任务、死锁、饥饿、线程不安全。对于多种任务的执行情况,无法监控。
总之,一般业务开发不建议使用。
Spring 拓展
// 官方示例
@Async
public CompletableFuture<User> findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}
使用@Async 注解和配置的线程池(一般是 ThreadPoolTaskExecutor),findUser 方法被代理后,会自动异步执行,无需手动 submit。实现了线程池和任务的解耦。
ThreadPoolTaskExecutor 支持回调:
@Deprecated(since = "6.0")
public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
// Spring 借鉴 Guava 自己实现的 ListenableFuture
ListenableFuture<?> submitListenable(Runnable task);
}
// 6.0 支持返回 CF
public interface AsyncTaskExecutor extends TaskExecutor {
default CompletableFuture<Void> submitCompletable(Runnable task) {
return CompletableFuture.runAsync(task, this);
}
}
TheadPoolExecutor 的回调方法改为可以通过参数配置:
public void setTaskDecorator(TaskDecorator taskDecorator) {
this.taskDecorator = taskDecorator;
// 使用组合实现继承功能:更好用和容易理解
}
可动态配置("corePoolSize", "maxPoolSize", "keepAliveSeconds"),可监控,提供如 getQueueSize, getActiveCount, getKeepAliveSeconds 等方法。
Spring 支持对线程池进行生命周期管理,通过自定义配置初始化,生命周期结束时销毁 bean:
// 父类实现生命周期相关方法
public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
implements BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean,
SmartLifecycle, ApplicationListener<ContextClosedEvent> {
@Override
public void afterPropertiesSet() {
// 初始化回调:InitializingBean#afterPropertiesSet
// 实际上这个方法可以 inline
initialize();
}
// 初始化模版,子类实现 initializeExecutor
public void initialize() {
if (logger.isDebugEnabled()) {
logger.debug("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (!this.threadNamePrefixSet && this.beanName != null) {
setThreadNamePrefix(this.beanName + "-");
}
this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
this.lifecycleDelegate = new ExecutorLifecycleDelegate(this.executor);
}
protected abstract ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler);
}
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
// 初始化核心逻辑
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler) {
@Override
public void execute(Runnable command) {
// 任务增强, 其他 submit 方法会复用 execute 方法,详见 AbstractExecutorService
Runnable decorated = command;
if (taskDecorator != null) {
decorated = taskDecorator.decorate(command);
if (decorated != command) {
decoratedTaskMap.put(decorated, command);
}
}
super.execute(decorated);
}
@Override
protected void beforeExecute(Thread thread, Runnable task) {
ThreadPoolTaskExecutor.this.beforeExecute(thread, task);
}
@Override
protected void afterExecute(Runnable task, Throwable ex) {
ThreadPoolTaskExecutor.this.afterExecute(task, ex);
}
};
// 其他配置信息
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
if (this.prestartAllCoreThreads) {
executor.prestartAllCoreThreads();
}
this.threadPoolExecutor = executor;
return executor;
}
}
再来看看优雅关闭的实现:
public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
implements BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean,
SmartLifecycle, ApplicationListener<ContextClosedEvent> {
@Override
public void destroy() {
// 实际上这个方法可以 inline
shutdown();
}
public void shutdown() {
if (logger.isDebugEnabled()) {
logger.debug("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
// 判断是否等待任务执行完
if (this.waitForTasksToCompleteOnShutdown) {
// 拒绝新任务
this.executor.shutdown();
}
else {
for (Runnable remainingTask : this.executor.shutdownNow()) {
// 取消任务
cancelRemainingTask(remainingTask);
}
}
// 等待完全关闭
awaitTerminationIfNecessary(this.executor);
}
}
// 取消任务逻辑,实际上任务已经包装为 FutureTask, 详见 AbstractExecutorService
protected void cancelRemainingTask(Runnable task) {
if (task instanceof Future<?> future) {
future.cancel(true);
}
}
private void awaitTerminationIfNecessary(ExecutorService executor) {
if (this.awaitTerminationMillis > 0) {
try {
// 等待完全关闭
if (!executor.awaitTermination(this.awaitTerminationMillis, TimeUnit.MILLISECONDS)) {
if (logger.isWarnEnabled()) {
logger.warn("Timed out while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
}
}
// 标准的中断处理方式: 日志+再打断
catch (InterruptedException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Interrupted while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
Thread.currentThread().interrupt();
}
}
}
}
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
// 取消剩余任务支持传播,包装任务 -> 取消原任务
@Override
protected void cancelRemainingTask(Runnable task) {
super.cancelRemainingTask(task);
// Cancel associated user-level Future handle as well
Object original = this.decoratedTaskMap.get(task);
if (original instanceof Future<?> future) {
future.cancel(true);
}
}
}
Guava:: ListeningExecutorService
// 需要使用 ListenableFuture 时,应该优先使用此接口
public interface ListeningExecutorService extends ExecutorService {
// 返回 Future 子类: ListenableFuture, 继承时,子类/子接口支持协变
<T extends @Nullable Object> ListenableFuture<T> submit(Callable<T> task);
ListenableFuture<?> submit(Runnable task);
<T extends @Nullable Object> ListenableFuture<T> submit(
Runnable task, @ParametricNullness T result);
// 略去 invokeAll, invokeAny 不推荐使用,均为阻塞方法
}
回调实现
// -33.2.1-jre
public abstract class AbstractListeningExecutorService extends AbstractExecutorService
implements ListeningExecutorService {
@Override
protected final <T extends @Nullable Object> RunnableFuture<T> newTaskFor(
Runnable runnable, @ParametricNullness T value) {
return TrustedListenableFutureTask.create(runnable, value);
}
}
// 为了完整性,以下代码展示了此 FutureTask 实现回调的具体过程
// 继承链比较长
class TrustedListenableFutureTask<V extends @Nullable Object> extends FluentFuture.TrustedFuture<V> implements RunnableFuture<V> {}
abstract static class TrustedFuture<V extends @Nullable Object> extends FluentFuture<V>
implements AbstractFuture.Trusted<V> {}
public abstract class FluentFuture<V extends @Nullable Object>
extends GwtFluentFutureCatchingSpecialization<V> {}
abstract class GwtFluentFutureCatchingSpecialization<V extends @Nullable Object>
extends AbstractFuture<V> {}
public abstract class AbstractFuture<V extends @Nullable Object> extends InternalFutureFailureAccess implements ListenableFuture<V> {}
// 具体实现在 AbstractFuture 中
public abstract class AbstractFuture<V extends @Nullable Object> extends InternalFutureFailureAccess implements ListenableFuture<V> {
@CheckForNull private volatile Listener listeners;
// 略去其他
public void addListener(Runnable listener, Executor executor) {
checkNotNull(listener, "Runnable was null.");
checkNotNull(executor, "Executor was null.");
if (!isDone()) {
// 新增 listener, 使用线程安全链表,《并发编程实战》有相似的例子
Listener oldHead = listeners; // 读操作
if (oldHead != Listener.TOMBSTONE) {
Listener newNode = new Listener(listener, executor);
do {
newNode.next = oldHead;
// 静态条件下,每次仅有一个新头部可以设置成功
if (ATOMIC_HELPER.casListeners(this, oldHead, newNode)) {
return;
}
oldHead = listeners; // re-read
} while (oldHead != Listener.TOMBSTONE);
}
}
// If we get here then the Listener TOMBSTONE was set, which means the future is done, call
// the listener.
// future 已完成 -> 直接执行
executeListener(listener, executor);
}
}
// 回调调用 complete 方法,在 ListenableFuture 设置值后调用
// 如 set(V), setException(Throwable), setFuture(ListenableFuture)方法
private static void complete(AbstractFuture<?> param, boolean callInterruptTask) {
// Declare a "true" local variable so that the Checker Framework will infer nullness.
AbstractFuture<?> future = param;
Listener next = null;
outer:
while (true) {
future.releaseWaiters();
if (callInterruptTask) {
future.interruptTask();
callInterruptTask = false;
}
future.afterDone();
// push the current set of listeners onto next
next = future.clearListeners(next);
future = null;
while (next != null) {
// 调用所有回调 listeners
Listener curr = next;
next = next.next;
// curr 记录了之前传参: task(Runable) + executor
Runnable task = requireNonNull(curr.task);
if (task instanceof SetFuture) {
SetFuture<?> setFuture = (SetFuture<?>) task;
future = setFuture.owner;
if (future.value == setFuture) {
Object valueToSet = getFutureValue(setFuture.future);
if (ATOMIC_HELPER.casValue(future, setFuture, valueToSet)) {
continue outer;
}
}
} else {
executeListener(task, requireNonNull(curr.executor));
}
}
break;
}
}
使用建议
使用官方推荐方式(装饰器模式)创建线程池,MoreExecutors.listeningDecorator(ExecutorService delegate) -> ListeningExecutorService。
netty 中的线程池与异步实现
EventExecutorGroup 继承自 ScheduledExecutorService,提交任务返回 netty 的 Future 实现(io.netty.util.concurrent.Future),Future 支持回调,支持优雅关闭和关闭回调。
EventExecutorGroup 用于通用的任务执行和管理,可以处理各种类型的任务,包括但不限于 IO 操作。 其实现 EventLoopGroup 专注于 IO 事件的处理和管理,支持注册 channel,通常用于处理网络连接、读取和写入操作。
提供了 Promise 接口,支持对 Future 的写操作。
总的来说,Netty 不愧为优秀的异步网络框架。
以下代码摘自 官方文档,事件/任务处理有三个线程池,bossGroup, workGroup 和 10 个线程的任务处理线程。
public void run() throws Exception {
// 创建两个 EventLoopGroup,bossGroup 用于接受连接,workerGroup 用于处理连接
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 单线程,负责处理接受连接事件
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 多线程,负责处理已连接的 IO 事件
try {
// 创建 ServerBootstrap 实例,用于启动服务器
ServerBootstrap b = new ServerBootstrap();
// 设置 bossGroup 和 workerGroup 到 ServerBootstrap
b.group(bossGroup, workerGroup)
// 指定使用 NIO 传输 Channel
.channel(NioServerSocketChannel.class)
// 设置处理新连接数据的处理器,并配置每个 Channel 的处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 添加处理器到 ChannelPipeline 中,这里使用了 DefaultEventExecutorGroup 来处理 channelRead 事件
ch.pipeline().addLast(new DefaultEventExecutorGroup(10), new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// channelRead 事件的处理逻辑,可以提交给 executorGroup 处理
}
});
}
})
// 设置参数
.option(ChannelOption.SO_BACKLOG, 128) // 等待连接的队列大小
.childOption(ChannelOption.SO_KEEPALIVE, true); // 保持连接活跃
// 绑定端口,开始接受进来的连接
ChannelFuture f = b.bind(port).sync();
// 等待服务器 socket 关闭
f.channel().closeFuture().sync();
} finally {
// 优雅地关闭线程池和释放所有的资源
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
动态线程池框架
DynamicTP 是基于配置中心的轻量级动态可监控线程池,提供了动态调参、通知报警、运行监控、三方包集成等功能。是对 JDK 提供的线程池的良好补充。限于篇幅所限,使用请参考 官方文档, 美团实践请参考 这篇文章。
总结
- 线程池的配置就像虚拟机的调优,似乎是一门艺术。
- 推荐使用线程池+异步支持类(如 CompletableFuture)实现并发需求,尽量不要使用底层代码。
- 对于 CPU 密集型任务,并发执行相比单线程执行不见得有性能提升,根据具体情形,需要对任务执行进行基准测试。
- 尽管标准库不尽如人意,实际项目中可以选择不同的增强类库,但是最好保持统一,避免混用。