线程池初始化严禁使用Executors

简介: 阿里巴巴规范禁止使用Executors创建线程池,因可能引发OOM。如newFixedThreadPool使用无界队列,任务堆积易导致内存溢出;newCachedThreadPool可创建过多线程,同样存在风险。推荐通过ThreadPoolExecutor或Guava方式手动创建线程池,明确设置队列容量和线程数,避免资源耗尽,提升系统稳定性。

使用线程池时候,我们可能会使用下面四个场景,这在alibaba代码规范中都是明令禁止的
// 创建一个单线程化的Executor[因为数量固定,可能会堆积大量请求,导致OOM]
private static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 创建一个固定数目线程的线程池[因为数量固定,可能会堆积大量请求,导致OOM]
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

// 创建一个可执行命令的单线程Executor[可能会创建大量的线程,导致OOM]
private static ExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

// 创建一个可缓存的线程池(60S存活时间)[可能会创建大量的线程,导致OOM]
private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
我们先来一个简单的例子,模拟一下使用 Executors 导致 OOM 的情况。
public class ExecutorsDemo {
private static ExecutorService executor = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
}
}
通过指定 JVM 参数:-Xmx8m -Xms8m 运行以上代码,会抛出 OOM:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
以上代码指出,ExecutorsDemo.java 的第 16 行,就是代码中的 executor.execute(new SubThread());。
通过上面的例子,我们知道了 Executors 创建的线程池存在 OOM 的风险,那么到底是什么原因导致的呢?我们需要深入 Executors 的源码来分析一下。其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致 OOM 的其实是 LinkedBlockingQueue.offer 方法。
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
如果读者翻看代码的话,也可以发现,其实底层确实是通过 LinkedBlockingQueue 实现的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
如果读者对 Java 中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。Java 中 的 BlockingQueue 主 要 有 两 种 实 现, 分 别 是 ArrayBlockingQueue 和 LinkedBlockingQueue。ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,必须设置容量。LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为 Integer.MAX_VALUE。这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置 LinkedBlockingQueue 的容量的话,其默认容量将会是 Integer.MAX_VALUE。 而 newFixedThreadPool 中创建 LinkedBlockingQueue 时,并未指定容量。此时,LinkedBlockingQueue 就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。上面提到的问题主要体现在 newFixedThreadPool 和 newSingleThreadExecutor 两个工厂方法上,并不是说newCachedThreadPool 和 newScheduledThreadPool 这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致 OOM

正确使用:
private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(10));
这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
但是部分alibaba作者更推荐使用guava创建对应的线程池,示例如下:
public class ExecutorsDemo {
private static ThreadFactory namedThreadFactory = new
ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), namedThreadFactory, new
ThreadPoolExecutor.
AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
pool.execute(new SubThread());
}
}
}
通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。

相关文章
|
1月前
|
分布式计算 监控 API
DMS Airflow:企业级数据工作流编排平台的专业实践
DMS Airflow 是基于 Apache Airflow 构建的企业级数据工作流编排平台,通过深度集成阿里云 DMS(Data Management Service)系统的各项能力,为数据团队提供了强大的工作流调度、监控和管理能力。本文将从 Airflow 的高级编排能力、DMS 集成的特殊能力,以及 DMS Airflow 的使用示例三个方面,全面介绍 DMS Airflow 的技术架构与实践应用。
|
22小时前
|
存储
初始化Map大小并非用多少指定多少
初始化HashMap时,指定容量并非直接生效,而是调整为最近的2的幂次(如1变2、3变4)。为避免扩容性能损耗,建议使用Guava的Maps.newHashMapWithExpectedSize(),或手动按公式“预期大小 / 0.75 + 1”设置初始容量,提升性能。
|
19小时前
|
文字识别 安全 数据处理
RAG分块应用中普遍存在的问题
RAG系统面临准确性、召回率与复杂文档解析三大挑战:模型易产生幻觉、检索噪声干扰、细粒度理解不足;语义匹配局限导致漏检,长尾知识覆盖差,多跳推理困难;表格、公式、图片等非结构化数据处理难,上下文割裂、逻辑结构丢失。合理分块策略是破局关键。
|
19小时前
基于LLM的分块
基于大语言模型(LLM)的智能分块技术,利用LLM语义理解能力,将非结构化文本按主题动态划分为语义连贯的文本块,适用于会议纪要、社交媒体等内容。虽分块质量高、适应性强,但计算成本高、可解释性差,依赖模型性能。
|
19小时前
|
人工智能 安全
MCP是什么?为何被称为AI时代的“USB-C”
MCP(模型上下文协议)是AI领域的“通用接口”,像USB-C一样让大模型便捷连接数据源与工具。它通过标准化上下文传递,实现信息互通与任务协同,确保每次调用都具备数据血统、策略与出处管理,推动AI无缝交互与安全可控运行。
|
19小时前
|
存储 人工智能 JSON
MCP工作的基本原理
MCP通过动态上下文窗口、多步骤工作流程支持与高效通信协议,实现智能、连贯的AI交互。动态窗口随对话扩展,记忆用户偏好与历史,压缩非关键信息;智能体可追踪操作、调整策略并自我优化,完成复杂任务;采用JSON-RPC 2.0协议,支持Stdio、HTTP+SSE及未来WebSocket,保障本地与网络环境下的高效通信,适用于客服、远程服务等多场景应用。
|
19小时前
|
XML 安全 数据格式
RAG面临的挑战与前沿探索
当前RAG面临知识关联缺失、推理与检索割裂、多模态理解弱、可信度难量化及长上下文建模难等深层问题。前沿探索聚焦检索增强、生成控制与优化分块,通过混合检索、查询扩展、递归推理、强制引用、语义分块等技术,推动RAG向动态交互、可解释、高可信方向演进。(238字)
|
19小时前
|
存储 人工智能 数据挖掘
MCP应用场景示例
MCP赋能智能数据分析与办公自动化:分析师输入需求,AI即连多源数据生成报告;办公助手通过MCP获取会议记录、整理并邮件分发,实现高效协同。
|
19小时前
|
人工智能 安全 API
MCP对大模型应用落地的价值
MCP构建了AI应用中本地与远程服务通信的标准桥梁,通过统一接口简化集成、扩展任务处理能力,并增强安全合规性,助力AI高效、安全完成复杂任务。
|
19小时前
|
人工智能 缓存 自然语言处理
大模型推理与应用术语解释
本系列介绍了大语言模型核心技术:推理实现高效生成,生成式AI创造多样化内容,检索增强生成提升准确性,提示工程优化输入引导,上下文学习实现零样本迁移,代理构建自主智能体,多模态学习融合多种数据形式,语义搜索理解深层意图。这些技术共同推动AI向更智能、更实用演进,广泛应用于内容生成、知识服务与自动化系统,成为现代人工智能发展的核心驱动力。(238字)