【面试题】那你再说说线程池的核心配置参数都是干什么的?平时我们应该怎么用?

ThreadPoolExecutor(int corePoolSize, // 1
                int maximumPoolSize,  // 2
                long keepAliveTime,  // 3
                TimeUnit unit,  // 4
                BlockingQueue<Runnable> workQueue, // 5
                ThreadFactory threadFactory,  // 6
                RejectedExecutionHandler handler )

【corePoolSize】核心线程数,没达到核心线程数时,会创建新的线程。当达到核心线程数时,任务会进去队列

【maximumPoolSize】最大线程数,可以为Integer.MAX_VALUE 21亿。当达到核心线程数时,会去创建额外的线程来执行任务,最多不超过最大线程数

【keepAliveTime】存活时间,当任务处理完成,额外的线程存活一段时间后,会自行销毁。空闲等待时间(该参数默认对核心线程无效,当allowCoreThreadTimeOut手动设置为true时,核心线程超过存活时间后才会被销毁)

【TimeUnit】空闲等待时间的单位

【queue】利用什么队列来存放任务,有界队列、无界队列等。BlockingQueue为无界队列

【ThreadFactory】线程创建工厂

【RejectExecutionHandler】拒绝策略

 

假设把queue做成有界队列,如下new ArrayBlockingQueue<Runnable>(200),那么假设corePoolSize个线程都在繁忙的工作,大量的任务进入有界队列,队伍也满了,此时怎么办?

如果maximumPoolSize最大线程数比corePoolSize核心线程数大,会额外创建新的线程放入线程池中,用于处理队列中的任务。这些额外的线程处理完一个任务后,会尝试从队列中获取新的任务继续执行

如果额外的线程用完了,而且也在繁忙中,队列也是满的。这个时候还有新任务怎么办?

通过拒绝策略将任务拒绝掉,有几种拒绝策略,可以传入RejectExecutionHandler

(1)AbortPolicy

(2)DiscardPolicy

(3)DiscardOldestPolicy

(4)CallRunsPolicy

(5)自定义拒绝策略

一般比较常用的是fixedThreadPool。可根据上述原理去定制自己的线程池,考虑到核心线程数、队列类型、最大线程数、拒绝策略以及线程释放时间。

 

 

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }

corePoolSize与maximumPoolSize相等,即其线程全为核心线程,是一个固定大小的线程池,是其优势;

keepAliveTime = 0 该参数默认对核心线程无效,而FixedThreadPool全部为核心线程;

workQueue 为LinkedBlockingQueue(无界阻塞队列),队列最大值Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。是其劣势;

FixedThreadPool的任务执行是无序的;

引用:线程池之ThreadPoolExecutor使用

【评论区】

1、在实际的使用中,应该如何合理配置核心线程数和队列长度呢?

线程池数量取决于业务逻辑,计算密集型还是io密集型,队列长度取决于队列中每个对象的大小,32位的系统和64位的采取指针压缩的系统的每个对象的大小也是不一样的,然后根据你jvm的启动参数,推算得出

2、如果corePoolSize大于maximunPoolSize,最多创建的线程数以哪个为准?

抛异常,throw new IllegalArgumentException();

3、线程池能创建多个吗,我们生产中有许多任务是单独创建线程池?

完全可以 ,ThreadPoolExecutor 是 new 出来的,不是单例的

4、如果线程池使用newFixedThreadPool创建,如果阻塞队列使用的是无界队列的话,那么永远不会新建额外线程处理任务,那么额外线程岂不是没用了? 为何要有这种设计呢? 而且这种方式容易导致任务堆积,造成OOM,岂不是很危险,那么为什么不直接使用有界队列和指定数量的最大线程数这种方式,一次解决这些隐患呢?

这个要考虑具体的使用场景,有写场景下不允许拒绝丢弃任务,这时候就只能等线程池的线程慢慢的去消化队列里面的任务。如果场景允许丢弃任务那丢弃完全没问题

corePoolsize和maxmunPoolSize的意思,核心池的数量如果等于最大线程池那就是只用核心池了,最大线程池的数量包括核心池的数量呢,在ThreadPoolExecutor类的构造方法中,如果maximumPoolSize < corePoolSize的话,会抛出IllegalArgumentException异常,工厂类的线程池方法,maxmunPoolSize如果是Integer.MAX_VALUE的话,核心池的数量一般是0,来一个任务创建一个线程,IO密集型的任务才会用到这个,至于具体的创建线程池,建议最好采用构造方法手动创建,并指定有界队列

 

优化Java线程池是提升并发性能、确保系统稳定性的重要手段,也是Java高级工程师在面试中必须掌握的核心知识点。线程池的优化不仅涉及核心参数配置,还包含任务调度策略、队列选择、拒绝策略、监控机制等多个方面。 ### 线程池核心参数配置优化 `ThreadPoolExecutor` 是 Java 中线程池核心实现类,其构造函数包含七个关键参数,合理配置这些参数是优化线程池的第一步: - **corePoolSize**:核心线程数,即使线程空闲也不会被回收,除非设置了`allowCoreThreadTimeOut`。 - **maximumPoolSize**:最大线程数,线程池允许创建的最大线程数量。 - **keepAliveTime**:非核心线程的空闲存活时间。 - **unit**:存活时间的单位。 - **workQueue**:任务队列,用于存放等待执行的任务。 - **threadFactory**:用于创建新线程的工厂。 - **handler**:拒绝策略,当线程池无法处理新任务时的处理方式。 在配置时,应根据任务类型(CPU密集型、IO密集型)和系统资源进行调整。例如,对于IO密集型任务,可以适当增加线程数量以提升吞吐量,而对于CPU密集型任务,则应避免线程过多导致上下文切换开销增大[^2]。 ### 任务队列选择 线程池的任务队列决定了任务的排队机制。常见的队列包括: - **ArrayBlockingQueue**:有界队列,适合资源敏感型任务。 - **LinkedBlockingQueue**:无界队列,默认情况下可能导致内存溢出。 - **SynchronousQueue**:不存储元素的阻塞队列,适合高并发、低延迟的场景。 选择合适的队列可以有效控制任务的排队行为,避免系统资源耗尽。 ### 拒绝策略配置线程池和队列都满时,线程池将启用拒绝策略。常见的拒绝策略包括: - **AbortPolicy**(默认):抛出异常。 - **CallerRunsPolicy**:由调用线程处理任务。 - **DiscardOldestPolicy**:丢弃队列中最老的任务。 - **DiscardPolicy**:静默丢弃任务。 在高并发系统中,建议使用`CallerRunsPolicy`,将压力反向传递给调用方,防止系统崩溃。 ### 线程池监控与调优 通过`ThreadPoolExecutor`提供的API可以获取线程池的运行状态,如当前线程数、任务队列大小、已完成任务数等。结合监控系统(如Prometheus、Micrometer)或使用Arthas等诊断工具进行运行时分析,有助于及时发现线程池瓶颈[^3]。 ### 常见面试题解析 1. **线程池的工作原理是什么?** 线程池通过复用线程对象来减少线程创建销毁的开销。新任务提交后,若当前线程数小于核心线程数,则创建新线程;否则尝试将任务放入队列;若队列满,则创建新线程(不超过最大线程数);若线程数已达上限且队列满,则执行拒绝策略[^1]。 2. **为什么使用线程池?其优势是什么?** 使用线程池可以有效控制并发线程数量,降低资源消耗,提高响应速度,便于管理线程生命周期。避免频繁创建和销毁线程带来的性能损耗[^4]。 3. **线程池有哪些拒绝策略?如何选择?** 常见拒绝策略包括默认的`AbortPolicy`、`CallerRunsPolicy`、`DiscardOldestPolicy`和`DiscardPolicy`。生产环境中建议使用`CallerRunsPolicy`,将压力反馈给调用者,避免系统崩溃。 4. **如何合理配置线程池核心参数?** 应根据任务类型(CPU密集型或IO密集型)和系统资源进行调整。例如,CPU密集型任务可设置`corePoolSize`为`CPU核心数 + 1`,IO密集型任务可适当增加线程数。 5. **线程池中线程是如何复用的?** 每个Worker线程是一个`Runnable`对象,内部通过循环不断从任务队列中取出任务并执行,从而实现线程的复用,避免频繁创建销毁线程带来的性能损耗。 6. **线程池的执行流程是怎样的?** 当提交任务时,线程池会判断当前线程数是否小于`corePoolSize`,若是则创建新线程执行任务;否则尝试将任务放入任务队列;若队列满,则判断当前线程数是否小于`maximumPoolSize`,若是则创建新线程;否则执行拒绝策略[^2]。 ### 示例代码:自定义线程池配置 ```java import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) { int corePoolSize = 5; int maximumPoolSize = 10; long keepAliveTime = 60; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); ThreadFactory threadFactory = Executors.defaultThreadFactory(); RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler ); for (int i = 0; i < 20; i++) { final int taskId = i; executor.execute(() -> { System.out.println("Executing task " + taskId); try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); } } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值