使用Executors类创建线程池的弊端以及与使用ThreadPoolExecutor类的区别

本文详细解析了线程池的工作原理,探讨了使用线程池的优势,包括资源消耗降低、响应速度提升及线程管理优化。同时,对比了Executors与ThreadPoolExecutor创建线程池的不同,强调了自定义参数的重要性,以避免资源耗尽的风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍:

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。

为什么要使用线程池?

这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处:

降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

如何创建线程池?

《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 实例的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

使用Executors哪里有风险?有什么风险?

Executors 返回线程池对象的弊端如下:

FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。

CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

下面就分析

分析源码:
先看Executors类,这里先看Executors类调用了哪些方法,至于方法参数的意义,先不用管,后面有详细介绍:
public class Executors {

    /**
     * 创建固定数量线程的线程池
     *
     * 这是最后一个参数--阻塞队列调用的方法,长度是Integer.MAX_VALUE
     * public LinkedBlockingQueue() {
     *    this(Integer.MAX_VALUE);
     * }
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


    /**
     * 创建只有一个线程的线程池
     *
     * 这是最后一个参数--阻塞队列调用的方法,长度也是Integer.MAX_VALUE
     * public LinkedBlockingQueue() {
     *    this(Integer.MAX_VALUE);
     * }
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }


    /**
     *创建一个缓冲线程池 
     *
     * 这是最后一个参数--阻塞队列调用的方法
     * public SynchronousQueue() {
     *    this(false);
     * }
     *
     * 它的第二个参数,maximumPoolSize 为Integer.MAX_VALUE
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    
    /**
     * Creates a thread pool that can schedule commands to run after a
     * given delay, or to execute periodically.
     *
     * 创建一个可以在给定延迟后再执行或定期执行命令的线程池
     *
     * ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的子类,代码如下:
     *
     public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
        implements ScheduledExecutorService {
            
            //这是下面调用的构造方法,其实是调用了父类的构造方法,这些参数都是下面分析的参数
            public ScheduledThreadPoolExecutor(int corePoolSize) {
               super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
               new DelayedWorkQueue());
            }
            
     }
    
     *
     * @param corePoolSize the number of threads to keep in the pool,
     * even if they are idle
     * @return a newly created scheduled thread pool
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
}

可以看到Executors类创建线程池的时候实际就是调用ThreadPoolExecutor类的构造方法来创建。

再看ThreadPoolExecutor类:

public class ThreadPoolExecutor extends AbstractExecutorService {


/**
     * 其中一个构造方法:newFixedThreadPool时调用的
     *
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
}

参数介绍:

corePoolSize

在线程池中保持的线程的数量,即使是这些线程没有被使用,除非设置了线程超时时间

maximumPoolSize

最大线程数量,当workQueue队列已满,放不下新的任务,再通过execute添加新的任务则线程池会再创建新的线程,线程数量大于corePoolSize但不会超过maximumPoolSize,如果超过maximumPoolSize,那么会抛出异常,如RejectedExecutionException。

keepAliveTime和unit

当线程池中线程数量大于workQueue,如果一个线程的空闲时间大于keepAliveTime,则该线程会被销毁。unit则是keepAliveTime的时间单位。

workQueue

阻塞队列,当线程池正在运行的线程数量已经达到corePoolSize,那么再通过execute添加新的任务则会被加到workQueue队列中,在队列中排队等待执行,而不会立即执行。

为什么说
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
再结合注释看Executors类中的传参就明白了!

Executors中创建线程池:

FixedThreadPool 和 SingleThreadExecutor 传入的最后一个参数阻塞队列 ”workQueue“,默认的长度是INTEGER.MAX_VALUE,而它们允许的最大线程数量又是有限的,所以当请求线程的任务过多线程不够用时,它们会在队列中等待,又因为队列的长度特别长,所以可能会堆积大量的请求,导致OOM。

CachedThreadPool 和 ScheduledThreadPool 它们的阻塞队列长度有限,但是传入的第二个参数maximumPoolSize 为Integer.MAX_VALUE,这就意味着当请求线程的任务过多线程不够而且队列也满了的时候,线程池就会创建新的线程,因为它允许的最大线程数量是相当大的,所以可能会创建大量线程,导致OOM。

总结:

Executors类中封装好的创建线程池的方法使用方便,但是也有其局限性和风险性,所以我们可以使用 ThreadPoolExecutor 类中的构造方法手动创建线程池的实例, 从而可以根据我们的使用情况来指定参数,满足使用的同时又能规避风险。
所以,说白了,使用Executors类创建线程池与使用ThreadPoolExecutor类的区别就是使用ThreadPoolExecutor类可以自定义传入我们设置的线程池的参数,更加灵活。

<think>嗯,用户这次的问题非常聚焦——要求详细解释Java中不推荐使用Executors创建线程池的原因。结合之前的对话历史,用户显然是在准备技术面试,需要深入理解线程池的底层机制和最佳实践。 用户引用的资料质量很高,特别是阿里巴巴开发手册的引用[^2]和OOM问题的分析[^3],这些都是面试官常问的点。用户可能希望获得一个既包含技术细节又便于记忆的回答框架,最好能附带实际案例。 从技术角度看,这个问题需要拆解三个层面: 第一是Executors的设计缺陷(固定线程池/缓存线程池的隐患),第二是资源耗尽的具体表现(OOM如何发生),第三是替代方案(ThreadPoolExecutor的正确用法)。尤其要强调LinkedBlockingQueue的无界特性这个致命点——这是大多数OOM的根源。 用户似乎对OOM机制特别关注,在引用[^3]做了标记。可以结合JVM内存模型解释:当核心线程阻塞时,无界队列持续堆积任务,最终撑爆堆空间。这个解释要配上简单的数学估算,比如每个任务对象大小×队列长度=内存占用。 面试场景下,用户可能需要一句话总结:“Executors的便捷性掩盖了资源,而ThreadPoolExecutor的显式参数让我们对线程池行为有完全掌控权”。这种表述既专业又简洁。 最后考虑补充一个对比表格,把四种默认线程池点可视化呈现。虽然原始引用[^4][^5]已提到部分内容,但表格更利于面试前快速复习。表格中要突出队列型和最大线程数的差异,这是问题的关键分水岭。</think>在Java中不推荐使用`Executors`创建线程池的核心原因在于**其预设的线程池配置存在资源耗尽(尤其是OOM)**,且**隐藏了关键参数配置细节**。以下是具体分析: --- ### 一、默认线程池的致命缺陷 `Executors`提供的四种常用线程池存在以下隐患: | **线程池型** | **底层实现** | **点** | |-------------------------|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| | `newFixedThreadPool` | `ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())` | **无界队列**:任务无限堆积,可能耗尽内存(OOM)[^3][^4] | | `newSingleThreadExecutor` | 同`FixedThreadPool`但线程数=1 | 同上,无界队列 | | `newCachedThreadPool` | `ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())` | **最大线程数=Integer.MAX_VALUE**:极端情况下创建海量线程,耗尽CPU/内存[^3][^5] | | `newScheduledThreadPool` | `ScheduledThreadPoolExecutor(corePoolSize)` | 使用`DelayedWorkQueue`(无界队列),同样存在OOM[^3] | --- ### 二、核心问题解析 1. **无界队列导致OOM** `FixedThreadPool`和`SingleThreadExecutor`使用`LinkedBlockingQueue`(默认容量=Integer.MAX_VALUE)。当任务提交速度 > 处理速度时,队列无限堆积任务对象,最终触发`OutOfMemoryError`[^3][^4]。 *示例*:若每个任务占用1MB内存,持续积压10万个任务,则额外消耗约100GB内存。 2. **无上限线程数耗尽资源** `CachedThreadPool`允许创建近乎无限的线程(上限$2^{31}-1$)。当高并发请求涌入时,可能瞬间创建大量线程,导致: - 线程上下文切换开销暴增,CPU满载 - 线程栈内存累积耗尽JVM内存(每个线程默认占用约1MB栈空间)[^3] 3. **关键参数不可控** `Executors`隐藏了线程池的核心参数(队列型、拒绝策略等),开发者无法根据业务场景定制: ```java // 危示例(无界队列) ExecutorService executor = Executors.newFixedThreadPool(10); // 安全做法(手动指定有界队列) ExecutorService safeExecutor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), // 有界队列 new ThreadPoolExecutor.CallerRunsPolicy() // 自定义拒绝策略 ); ``` --- ### 三、解决方案:手动创建线程池 通过`ThreadPoolExecutor`构造函数显式控制参数[^4][^5]: ```java new ThreadPoolExecutor( corePoolSize, // 核心线程数(常驻线程) maximumPoolSize, // 最大线程数(突发流量时扩容上限) keepAliveTime, // 非核心线程空闲存活时间 unit, // 时间单位 new ArrayBlockingQueue<>(capacity), // 有界队列(必须!) Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 ); ``` **关键配置原则**: 1. **队列必须设上限**:根据内存容量设定队列大小(如`new ArrayBlockingQueue<>(200)`) 2. **定义拒绝策略**(四选一): - `AbortPolicy`(默认):抛出`RejectedExecutionException` - `CallerRunsPolicy`:由提交任务的线程执行 - `DiscardPolicy`:静默丢弃任务 - `DiscardOldestPolicy`:丢弃队列头部的任务 3. **合理计算线程数**: - CPU密集型:$corePoolSize = CPU核数 + 1$ - I/O密集型:$corePoolSize = CPU核数 \times (1 + \frac{平均等待时间}{平均计算时间})$ --- ### 四、真实案例对比 | **场景** | `Executors.newFixedThreadPool(10)` | **手动创建线程池** | |------------------------|-----------------------------------|---------------------------------------------| | 持续接收10000个任务 | 队列堆积9900个任务 → OOM | 队列满100后触发拒绝策略,系统保持稳定 | | 突发1000并发请求 | 创建大量线程 → CPU 100% | 最大线程数=50,队列满后拒绝,保护系统资源 | > 阿里巴巴Java开发手册明确禁止使用`Executors`创建线程池,强制要求通过`ThreadPoolExecutor`显式构建[^2]。 --- ### 总结回答策略(面试场景) 1. **直接点明**: “不推荐使用`Executors`是因为其预设的线程池使用无界队列或无限扩增线程,可能引发OOM或资源耗尽。” 2. **对比解决方案**: “应改用`ThreadPoolExecutor`手动指定有界队列(如`ArrayBlockingQueue`)和合理的拒绝策略。” 3. **引申设计思想**: “这体现了‘资源可控’的设计原则,避免便捷性掩盖潜在——这也是阿里开发手册的强制要求[^2]。” ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值