面试题答案
一键面试- 调整线程池核心参数
- 核心线程数(corePoolSize):根据系统资源(如CPU核心数、内存等)和预估的并发查询负载来确定。对于I/O密集型的ElasticSearch查询,可适当增加核心线程数,一般经验公式为
核心线程数 = CPU核心数 * (1 + 平均I/O等待时间 / 平均CPU处理时间)
。例如,如果CPU有8核心,平均I/O等待时间是10ms,平均CPU处理时间是1ms,那么核心线程数可设为8*(1 + 10/1)=88
。 - 最大线程数(maximumPoolSize):设置一个合理的上限,避免过多线程导致系统资源耗尽。可以通过压力测试,观察系统在不同线程数下的性能表现来确定。如果系统在达到100个线程后,性能不再提升反而下降,那么最大线程数可设置略小于这个值,如90。
- 队列容量(workQueue):选择合适的队列类型和容量。对于高并发查询场景,可使用无界队列(如
LinkedBlockingQueue
),但要注意可能会导致内存耗尽问题;也可使用有界队列(如ArrayBlockingQueue
),根据预估的查询请求量设置合理的容量,如500,防止过多请求堆积。
- 核心线程数(corePoolSize):根据系统资源(如CPU核心数、内存等)和预估的并发查询负载来确定。对于I/O密集型的ElasticSearch查询,可适当增加核心线程数,一般经验公式为
- 优化线程创建策略
- 使用预启动核心线程:通过调用
ThreadPoolExecutor.prestartAllCoreThreads()
方法,在线程池创建时就启动所有核心线程,避免在高并发查询时临时创建线程带来的开销。 - 控制线程创建速度:如果线程创建过快可能会导致系统资源瞬间耗尽。可以通过设置
RejectedExecutionHandler
来控制新线程的创建。例如,使用ThreadPoolExecutor.CallerRunsPolicy
,当线程池和队列都满时,让提交任务的线程自己执行任务,这样可以减缓任务提交速度,给线程池处理任务的时间。
- 使用预启动核心线程:通过调用
- 优化任务调度方式
- 使用优先级队列:创建一个实现
PriorityBlockingQueue
的任务队列,根据查询任务的优先级来调度。比如,对于一些重要用户或者实时性要求高的查询任务,设置较高优先级,优先执行。 - 采用公平调度策略:如果任务的公平性比较重要,可使用
FairSync
机制。在Java线程池中,可以通过自定义RejectedExecutionHandler
和任务队列来实现公平调度,保证每个任务都有机会被执行,避免某些任务长时间等待。
- 使用优先级队列:创建一个实现
- 线程池监控与动态调整
- 监控线程池状态:通过
ThreadPoolExecutor.getTaskCount()
、ThreadPoolExecutor.getCompletedTaskCount()
、ThreadPoolExecutor.getActiveCount()
等方法,实时监控线程池的任务数量、已完成任务数量和活动线程数等指标。可以将这些指标通过监控工具(如Prometheus + Grafana)展示出来,以便及时发现性能瓶颈。 - 动态调整线程池参数:基于监控数据,使用动态代理或者配置中心来动态调整线程池的核心参数。例如,当发现任务队列持续满时,适当增加核心线程数或者队列容量;当发现线程池中有大量空闲线程时,适当减少线程数。
- 监控线程池状态:通过