面试题答案
一键面试线程池参数调整
- 核心线程数(corePoolSize):
- 根据任务的性质和预计的并发量来合理设置。如果任务是CPU密集型,核心线程数一般设置为
CPU核心数 + 1
,因为CPU密集型任务主要消耗CPU资源,多一个线程可以在某个线程因偶尔的I/O等原因阻塞时,充分利用CPU资源。例如,在一个图像渲染的项目中,每个任务都需要大量的CPU计算,服务器是8核CPU,将核心线程数设置为9。 - 如果任务是I/O密集型,核心线程数可以设置为
2 * CPU核心数
。因为I/O密集型任务在等待I/O操作完成时,CPU处于空闲状态,更多的线程可以在I/O等待时充分利用CPU资源。比如在一个文件读取和处理的项目中,将核心线程数设置为16(假设8核CPU)。
- 根据任务的性质和预计的并发量来合理设置。如果任务是CPU密集型,核心线程数一般设置为
- 最大线程数(maximumPoolSize):
- 要考虑系统资源的承受能力。在高并发场景下,最大线程数不能设置过大,否则会消耗过多的系统资源(如内存等)。一般先通过压测确定系统在可接受性能下降范围内能承载的最大并发线程数。例如,在一个电商秒杀系统的测试中,发现当线程数超过500时,系统响应时间急剧上升,于是将最大线程数设置为500。
- 最大线程数与核心线程数的差值要合理,差值过大会导致线程频繁创建和销毁,增加系统开销;差值过小则在高并发时无法充分利用系统资源。
- 线程存活时间(keepAliveTime):
- 对于非核心线程,存活时间不宜过长,否则会占用资源。在高并发场景下,可以适当缩短这个时间,让闲置的非核心线程尽快被回收。比如设置为5 - 10秒,当非核心线程在这段时间内没有任务执行,就会被销毁。
任务队列类型选择
- 无界队列(如LinkedBlockingQueue):
- 优点是理论上可以存放无限数量的任务,在一定程度上可以防止任务被拒绝。但在高并发时,可能会导致大量任务堆积在队列中,占用大量内存,甚至引发OOM(OutOfMemoryError)。所以一般不适合高并发且任务执行时间较长的场景。例如,在一个日志收集系统中,任务执行相对较快,且对内存消耗不太敏感,可以使用LinkedBlockingQueue。
- 有界队列(如ArrayBlockingQueue):
- 明确指定队列容量,能有效控制任务堆积的数量,避免内存耗尽问题。在高并发场景下,当队列满时,就会触发拒绝策略。比如在一个实时交易处理系统中,将队列容量设置为1000,当队列中的任务达到1000个时,新任务就会根据拒绝策略处理,这样可以保证系统不会因为任务无限堆积而崩溃。
- 同步移交队列(SynchronousQueue):
- 该队列不存储任务,任务直接从生产者移交到消费者线程。适用于任务处理速度非常快的场景,能避免任务在队列中等待,减少任务的排队时间。例如在一个高频交易系统中,交易处理任务可以快速完成,使用SynchronousQueue可以提高系统的响应速度。
拒绝策略优化
- AbortPolicy(默认策略):
- 当任务队列满且线程池达到最大线程数时,直接抛出RejectedExecutionException异常。在一些对异常处理比较严格的系统中,不适合使用这种策略,因为它可能导致任务丢失。例如在一个订单处理系统中,如果使用AbortPolicy,订单任务可能会因为拒绝而丢失,影响业务。
- CallerRunsPolicy:
- 当任务被拒绝时,由提交任务的线程来执行该任务。这样可以减少新任务进入线程池和队列,缓解线程池和队列的压力。在一些对任务实时性要求不是特别高的场景下适用。比如在一个后台数据统计任务系统中,使用CallerRunsPolicy,即使任务被拒绝,提交任务的线程也可以在合适的时候执行任务,不会丢失任务。
- DiscardPolicy:
- 直接丢弃被拒绝的任务,不做任何处理。这种策略适用于一些对任务可靠性要求不高,且任务执行与否对系统整体影响不大的场景。例如在一个简单的广告推送任务系统中,如果任务被拒绝,丢弃任务对系统核心业务影响较小。
- DiscardOldestPolicy:
- 丢弃队列中最老的任务,然后尝试重新提交当前任务。在任务队列有一定的优先级概念,且新任务相对更重要的场景下适用。比如在一个实时路况信息更新系统中,新的路况信息任务更重要,当队列满时,丢弃旧的路况任务,尝试提交新任务。
- 自定义拒绝策略:
- 根据项目的具体需求实现RejectedExecutionHandler接口来自定义拒绝策略。例如在一个金融交易系统中,可以实现一个拒绝策略,将被拒绝的任务记录到日志中,并尝试在系统负载降低时重新提交任务,以保证交易任务的可靠性。