面试题答案
一键面试1. 线程池大小的确定
- 核心线程数:根据业务场景中的CPU核心数以及任务类型来确定。对于I/O密集型任务,核心线程数可以设置为CPU核心数的2倍左右,因为I/O操作等待时间长,线程在等待I/O时CPU是空闲的,更多线程可以充分利用CPU。例如在一个4核心CPU的服务器上,I/O密集型任务核心线程数可设为8。对于CPU密集型任务,核心线程数设置为CPU核心数即可,以避免过多线程竞争CPU资源导致上下文切换开销增大。
- 最大线程数:最大线程数要考虑系统资源的限制,不能设置过大。如果系统内存有限,过多线程可能导致内存不足。一般可根据服务器的内存、网络带宽等资源进行估算。比如对于高并发且读写频繁的业务,如果网络带宽是瓶颈,需要根据每次读写操作的网络流量以及带宽上限来限制最大线程数,防止网络拥塞。
2. 拒绝策略的选择
- AbortPolicy:该策略是默认策略,当任务队列已满且线程池达到最大线程数时,新任务会被拒绝并抛出RejectedExecutionException异常。在对任务丢失零容忍的场景下,业务代码可以捕获该异常并进行相应处理,比如记录日志、重试任务等。
- CallerRunsPolicy:当任务被拒绝时,会在调用execute方法的线程中直接执行该任务。适用于对性能要求不是特别高,但希望能处理所有任务的场景,这样可以防止任务丢失,不过可能会影响调用线程的性能。
- DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理。这种策略适用于对任务执行结果不敏感,且任务量非常大的场景,比如一些日志记录任务,偶尔丢失几条日志对整体业务影响不大。
- DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试提交新任务。适用于任务队列中的任务时效性较低,新任务更重要的场景,例如实时数据处理业务,新数据比旧数据更有价值。
3. 任务队列的选择
- ArrayBlockingQueue:基于数组的有界阻塞队列,在初始化时需要指定队列容量。由于其有界性,可以防止任务队列无限增长导致内存耗尽。适用于对任务数量有明确上限预期的业务场景,例如订单处理系统,可根据系统处理能力设置合理的队列容量,防止过多订单堆积。
- LinkedBlockingQueue:基于链表的无界阻塞队列(也可以指定容量变为有界队列)。如果使用无界队列,在高并发场景下可能会导致任务大量堆积,耗尽内存,所以一般建议指定队列容量。它适用于任务处理速度相对稳定,不会突然产生大量任务的场景,比如常规的用户请求处理。
- SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。适用于任务执行非常迅速,不希望任务在队列中等待的场景,可减少任务排队带来的延迟,例如实时交易处理场景。
4. 线程工厂的定制
- 可以自定义线程工厂,设置线程的优先级、名称等属性。通过设置合理的线程优先级,比如对于关键业务的任务线程设置较高优先级,可使这些任务优先执行,提高业务的响应速度。同时,给线程设置有意义的名称,方便在日志和监控中定位问题,例如以业务模块名称加上线程编号作为线程名称。
5. 监控与调优
- 在ThreadPool类中添加监控指标,如任务提交数量、任务完成数量、线程池活跃线程数、队列中的任务数等。通过定期收集这些指标数据,可以分析线程池的运行状态。例如,如果发现队列中的任务数经常接近或达到队列容量上限,可能需要调整线程池大小或任务处理逻辑;如果活跃线程数长期处于较低水平,可能说明线程池过大,造成资源浪费,需要适当减小线程池规模。根据监控数据不断对线程池的初始化参数进行调优,以达到性能和稳定性的最佳平衡。