面试题答案
一键面试线程池参数选择依据
- 核心线程数(corePoolSize):
- 考虑金融交易系统中常见的并发任务数量。例如,分析历史交易数据可知,在正常业务时段,平均有
N
个订单处理任务同时进行,核心线程数可设置为略大于N
,如N + M
(M
为一个较小的正数,如3 - 5),以应对偶尔的突发小流量。这样可以保证常见业务负载下线程池中有足够的线程立即处理任务,避免频繁创建新线程的开销。
- 考虑金融交易系统中常见的并发任务数量。例如,分析历史交易数据可知,在正常业务时段,平均有
- 最大线程数(maximumPoolSize):
- 需考虑系统可能承受的最大并发量。假设经过性能测试和业务预估,系统在极端情况下(如促销活动期间),订单处理任务并发量可能达到
X
。最大线程数可设置为X
,但要结合系统资源(如内存、CPU等)来确定。如果系统资源有限,不能无限增加线程数,防止因过多线程导致系统资源耗尽。例如,每个线程占用一定的内存空间,若内存总量为Y
,每个线程平均占用Z
内存,那么线程数上限应满足maximumPoolSize * Z <= Y
。
- 需考虑系统可能承受的最大并发量。假设经过性能测试和业务预估,系统在极端情况下(如促销活动期间),订单处理任务并发量可能达到
- 队列容量(workQueue):
- 选择有界队列,如
ArrayBlockingQueue
。队列容量的设置要综合考虑系统处理能力和任务堆积容忍度。如果设置过小,可能导致任务频繁拒绝;设置过大,任务在队列中等待时间过长,影响实时性。假设系统平均处理一个订单任务的时间为T
,预估业务高峰时段每秒新增订单数为P
,若希望任务在队列中的等待时间不超过W
秒,队列容量Q
可估算为Q = P * W
。例如,P = 100
,W = 5
,则Q = 500
。
- 选择有界队列,如
- 线程存活时间(keepAliveTime):
- 当线程池中的线程数量超过核心线程数时,多余的空闲线程在存活时间后会被销毁。对于金融交易系统,由于任务处理相对持续,可设置一个适中的存活时间,如5 - 10秒。这样既不会让空闲线程长时间占用资源,又能在短时间内应对突发流量,避免频繁创建和销毁线程。
任务调度方式
- 优先级调度:
- 自定义任务类实现
Comparable
接口,在compareTo
方法中根据任务优先级进行排序。例如,高优先级的金融交易订单任务优先处理。 - 使用
PriorityBlockingQueue
作为线程池的工作队列,线程池从队列中获取任务时,会按照优先级顺序获取,优先处理高优先级任务。
- 自定义任务类实现
- 超时处理:
- 利用
Future
接口和Callable
接口实现任务超时处理。在提交任务时,使用ExecutorService.submit(Callable<T> task)
方法获取Future
对象。 - 调用
Future.get(timeout, TimeUnit unit)
方法设置任务超时时间。如果任务在指定时间内未完成,抛出TimeoutException
,可在捕获该异常后进行相应处理,如记录日志、回滚部分已执行的操作、向用户提示任务超时等。
- 利用
异常处理
- 任务执行异常:
- 当任务执行过程中抛出异常时,线程池默认行为是终止当前线程。为了不影响整个系统的稳定性,可使用
ThreadPoolExecutor.CallerRunsPolicy
拒绝策略。当线程池和队列都已满,无法处理新任务时,该策略会让调用者线程来执行任务,这样可以避免任务丢失,同时将异常抛给调用者线程,在调用者线程中捕获并处理异常,如记录详细的异常信息、进行错误恢复操作等。
- 当任务执行过程中抛出异常时,线程池默认行为是终止当前线程。为了不影响整个系统的稳定性,可使用
- 资源限制相关异常:
- 在任务执行前,对资源进行检查和预分配。例如,在处理金融交易订单时,检查可用资金等资源。如果资源不足,抛出自定义异常(如
InsufficientResourceException
),在捕获该异常后,向用户提示资源不足信息,并暂停相关任务的执行,避免无效操作。同时,系统可定期检查资源状态,当资源充足时,重新尝试执行任务。
- 在任务执行前,对资源进行检查和预分配。例如,在处理金融交易订单时,检查可用资金等资源。如果资源不足,抛出自定义异常(如
通过以上基于Executors
创建线程池的策略,可以有效确保金融交易系统订单处理的高性能、可靠性和稳定性。