面试题答案
一键面试线程池参数调整
- 核心线程数(corePoolSize):
根据系统的任务负载特性和可用的CPU核心数来设定。对于I/O密集型任务,核心线程数可适当大于CPU核心数,例如
CPU核心数 * 2
,因为I/O操作会使线程处于等待状态,更多线程可以充分利用CPU时间。对于CPU密集型任务,核心线程数应接近或等于CPU核心数,以避免过多线程竞争CPU资源导致性能下降。 - 最大线程数(maximumPoolSize): 在确定最大线程数时,需要考虑系统的资源限制,如内存。不能设置过大,否则可能导致系统资源耗尽。一般来说,最大线程数可根据任务的突发量以及系统能承受的额外负载来设定。例如,如果预计任务量可能会突然增加一倍,且系统资源允许,可以将最大线程数设置为核心线程数的2倍,但需通过性能测试来确定最优值。
- 线程存活时间(keepAliveTime): 对于任务执行时间较短且频率较高的场景,可适当延长线程存活时间,减少线程创建和销毁的开销。比如设置为5 - 10秒,这样当线程执行完任务后不会立即销毁,而是等待一段时间,若有新任务到来可直接复用。对于执行时间较长且任务间隔不固定的场景,较短的存活时间(如1 - 2秒)可能更合适,以释放闲置线程占用的资源。
任务队列设计
- 有界队列:
使用
ArrayBlockingQueue
等有界队列。根据系统预估的任务峰值来设置队列容量,防止任务无限堆积导致内存溢出。例如,若系统在高负载下预计最多有1000个任务同时等待执行,可将队列容量设置为1000。有界队列可以更好地控制资源消耗,当队列满时,可触发相应的拒绝策略。 - 无界队列:
在某些情况下,如任务执行速度较快且不会出现大量任务堆积的场景,可使用
LinkedBlockingQueue
(默认无界)。但要谨慎使用,因为如果任务产生速度远大于处理速度,可能会导致内存耗尽。此时需要密切监控系统的内存使用情况。 - 优先级队列:
若任务有不同的优先级需求,可使用
PriorityBlockingQueue
。为任务定义实现Comparable
接口的优先级类,根据任务的紧急程度或重要性设置优先级。这样线程池会优先处理高优先级的任务,满足系统对不同任务的时间敏感需求。
拒绝策略优化
- 默认拒绝策略(AbortPolicy):
默认策略是直接抛出
RejectedExecutionException
。在一些对任务执行要求严格,不允许任务丢失的场景下,可捕获该异常并进行日志记录,然后尝试重新提交任务,例如通过一个重试机制,在一定时间间隔后再次提交任务,直到任务成功提交到线程池。 - 抛弃策略(DiscardPolicy): 此策略直接丢弃被拒绝的任务。在任务不是非常重要,且系统负载过高需要快速处理核心任务的场景下可以使用。但为了便于后续分析,可对被丢弃的任务进行简单的日志记录,记录任务的基本信息和丢弃时间。
- 抛弃最旧策略(DiscardOldestPolicy): 该策略会丢弃队列中最旧的任务,然后尝试提交新任务。适用于任务队列中有一些长时间等待且可能已经过期的任务场景。同样,可对被丢弃的旧任务进行日志记录,以便分析任务处理情况。
- 自定义拒绝策略:
实现
RejectedExecutionHandler
接口来自定义拒绝策略。例如,将被拒绝的任务存储到一个持久化存储(如数据库或文件系统)中,待系统负载降低时,通过一个独立的线程从存储中读取任务并重新提交到线程池。或者将任务发送到消息队列中,由其他系统进行处理,以保证任务不会丢失且能在合适的时机继续执行。