在阿里巴巴的《java开发手册》中有明确规定:
【强制】线程池不允许使用
executors去创建,而是通过threadpoolexecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
阿里禁止使用executors并不是说这个类的方法本身有致命的bug,而是因为它隐藏了资源耗尽的风险,不符合大厂对于系统稳定性和可观测性的苛刻要求。
下面我们以newfixedthreadpool方法为例详细拆解一下它的缺陷以及阿里的考量。
一、executors.newfixedthreadpool()的底层原理与核心缺陷
首先,我们来看一下这个方法的源码:
public static executorservice newfixedthreadpool(int nthreads) {
return new threadpoolexecutor(nthreads, nthreads,
0l, timeunit.milliseconds,
new linkedblockingqueue<runnable>());
}从源码可以看到,它创建了一个线程池,其核心参数是:
- 核心线程数 = 最大线程数:
nthreads - 空闲线程存活时间:0(因为核心线程数和最大线程数相等,这个参数失效)
- 工作队列:
new linkedblockingqueue<runnable>()【这是问题的核心】
核心缺陷:无界的工作队列
linkedblockingqueue 在默认情况下(无参构造)是一个 无界队列。这意味着它的容量是 integer.max_value,理论上可以无限地向其中添加任务。
这会导致一个严重的问题:当任务提交的速度持续超过线程处理的速度时,任务会在无界队列中无限堆积。
这个“无限堆积”会引发一系列连锁反应:
1、内存耗尽(oom):
- 每个排队等待执行的任务都会占用一定的内存(例如,任务对象本身、捕获的上下文变量等)。
- 如果任务产生速度极快(如突发流量、循环中提交任务),而处理速度很慢(如依赖的外部服务响应慢),队列中的任务会越来越多,最终占满整个堆内存,导致
outofmemoryerror: java heap space。这会直接导致整个应用崩溃,而不是仅仅线程池不可用。
2、延迟和响应缓慢:
- 即使没有达到oom,任务在队列中排队的时间也会非常长。一个任务可能被提交后很久才得到执行,导致系统整体响应时间变得不可接受。
3、问题定位困难:
- 由于队列是无界的,系统在oom之前可能看起来“正常”运行,只是有点慢。当oom发生时,你只能看到一个内存溢出的错误,但很难快速定位到是哪个线程池、为什么积累了这么多任务。缺乏必要的监控和预警。
二、 为什么阿里等大厂明确禁止使用?
禁止的原因正是基于上述缺陷,并上升到工程规范和架构层面:
1、规避风险,保证系统稳定性:
- 大厂的应用通常是高并发、海量数据的场景,对稳定性要求极高。任何可能导致服务雪崩或oom的风险都必须从源头规避。使用有界队列是构建“韧性系统”的基本要求。
2、提倡资源管理的显式化:
executors提供的工厂方法像是一个“黑盒”,它隐藏了threadpoolexecutor的关键参数(如队列类型和大小)。通过强制使用threadpoolexecutor构造函数,开发者必须显式地设置核心线程数、最大线程数、队列容量、拒绝策略等。这个过程迫使开发者去思考线程池的配置是否合理,加深对线程池运行机制的理解。
3、需要合理的拒绝策略:
- 当使用有界队列时,队列满了之后,就需要拒绝策略来处理新提交的任务。
threadpoolexecutor提供了几种内置策略,如:abortpolicy(默认):抛出rejectedexecutionexception,让调用者感知到异常。callerrunspolicy:让提交任务的线程自己去执行它,这相当于一种负反馈,能有效平缓任务提交速率。discardpolicy/discardoldestpolicy:丢弃任务。
- 通过选择合适的拒绝策略,可以在系统过载时提供一种“优雅降级”的方案,而不是像无界队列那样默默地吃掉所有内存直至崩溃。
三、 正确的使用姿势
应该直接使用 threadpoolexecutor 来创建线程池,并为其设置一个有界的工作队列。
import java.util.concurrent.*;
public class safethreadpoolexample {
public static void main(string[] args) {
// 核心参数
int corepoolsize = 10;
int maximumpoolsize = 20;
long keepalivetime = 60l;
int queuecapacity = 1000; // 关键:设置队列容量
// 手动创建线程池
threadpoolexecutor executor = new threadpoolexecutor(
corepoolsize,
maximumpoolsize,
keepalivetime,
timeunit.seconds,
new linkedblockingqueue<>(queuecapacity), // 有界队列
new threadfactory() { // 可选:自定义线程工厂,便于命名和监控
@override
public thread newthread(runnable r) {
thread t = new thread(r, "myapp-thread-" + r.hashcode());
t.setdaemon(false);
return t;
}
},
new threadpoolexecutor.callerrunspolicy() // 关键:设置合适的拒绝策略
);
// 使用 executor 提交任务...
// executor.execute(() -> { ... });
// 优雅关闭
// executor.shutdown();
}
}总结
| 特性/方式 | executors.newfixedthreadpool() | 手动 threadpoolexecutor |
|---|---|---|
| 队列类型 | 无界 (linkedblockingqueue) | 有界 (可配置,如 new linkedblockingqueue<>(capacity)) |
| 资源风险 | 高,可能导致内存溢出(oom) | 低,通过容量控制和管理 |
| 拒绝策略 | 初期无效,oom前相当于无拒绝 | 有效,队列满后触发,可预测系统行为 |
| 可观测性 | 差,队列长度无上限,监控困难 | 好,队列有界,便于监控队列堆积情况 |
| 阿里规范 | 禁止 | 推荐 |
结论:executors.newfixedthreadpool() 的主要缺陷在于其无界工作队列带来的内存溢出风险。阿里等大厂禁止使用主要是为了规避上述风险,同时培养开发者的资源边界意识和风险意识,强制通过 threadpoolexecutor 的显式配置来构建更健壮、更可控、更易于监控的线程池,从而保障大规模分布式系统的稳定性。
到此这篇关于为什么阿里禁止使用executors去创建线程池的文章就介绍到这了,更多相关阿里禁止使用executors创建线程池内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论