面试题答案
一键面试线程池类型选择
- 使用
ThreadPoolExecutor
:它是Java中最基础的线程池实现,可灵活配置参数。在HBase高并发随机读写场景下,可根据业务特点精细调整核心线程数、最大线程数等参数。相比于FixedThreadPool
(固定线程数,无法应对突发流量)和CachedThreadPool
(线程数无上限,可能耗尽资源),ThreadPoolExecutor
能更好地控制资源使用。 - 考虑
ScheduledThreadPoolExecutor
(可选):若有定时任务需求,例如定期清理缓存、检查数据一致性等,可使用ScheduledThreadPoolExecutor
。它继承自ThreadPoolExecutor
,在ThreadPoolExecutor
基础上增加了定时任务执行功能。
线程池参数调整
- 核心线程数:
- 确定依据:根据HBase集群的硬件资源(如CPU核心数、内存大小)以及预估的并发请求量来确定。一般来说,可以参考CPU核心数,例如对于多核CPU服务器,核心线程数可设置为CPU核心数的1 - 2倍。假设服务器有8个CPU核心,核心线程数可设置在8 - 16之间。这是因为HBase的随机读写操作通常会涉及网络I/O和磁盘I/O,而I/O操作相对CPU计算来说是异步的,更多的核心线程可以在一个线程等待I/O时,让其他线程继续处理请求,充分利用CPU资源。
- 动态调整:可以使用
ThreadPoolExecutor
的setCorePoolSize
方法根据系统负载动态调整核心线程数。例如,在业务高峰期适当增加核心线程数,在低谷期减少核心线程数以节省资源。
- 最大线程数:
- 确定依据:最大线程数要考虑系统的资源上限,不能设置过高导致系统资源耗尽。一般可设置为核心线程数的2 - 4倍。继续以8核CPU服务器为例,核心线程数设为12时,最大线程数可设置在24 - 48之间。这样在高并发突发流量时,线程池能临时增加线程处理请求,但又不会因为线程数过多而导致系统资源紧张。
- 结合队列容量:最大线程数和队列容量是相互关联的。如果队列容量较大,那么最大线程数可以适当减小,因为队列可以缓冲一部分请求;反之,如果队列容量较小,最大线程数则需要适当增大以应对突发请求。
- 队列容量:
- 无界队列:如果使用无界队列(如
LinkedBlockingQueue
不指定容量),理论上可以无限缓冲请求,但可能会导致大量请求在队列中堆积,占用大量内存,并且在队列已满且达到最大线程数时,新请求才会被拒绝。在HBase高并发随机读写场景下,若业务允许一定的请求延迟且对内存资源有足够保障,可考虑使用无界队列。 - 有界队列:使用有界队列(如
ArrayBlockingQueue
)可以限制请求的堆积,避免内存耗尽。队列容量的设置要结合预估的并发请求量和系统处理能力。例如,预估平均每秒有100个请求,每个请求处理时间平均为0.1秒,那么队列容量可设置为100 * 0.1 = 10左右,并根据实际运行情况进行调整。如果队列容量过小,可能会导致大量请求被拒绝;容量过大,又可能导致请求长时间在队列中等待。
- 无界队列:如果使用无界队列(如
- 拒绝策略:
- AbortPolicy:默认的拒绝策略,当线程池无法处理新任务(队列已满且达到最大线程数)时,会抛出
RejectedExecutionException
。在HBase场景下,如果业务对请求失败比较敏感,不允许直接丢弃请求,这种策略不太合适。 - CallerRunsPolicy:当任务被拒绝时,由提交任务的线程(调用者)来执行该任务。这可以减轻线程池的压力,但可能会影响调用者的正常工作。在HBase中,如果调用者是客户端程序,这种策略可能会导致客户端响应变慢。
- DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理。适用于对数据准确性要求不高,允许部分请求丢失的场景,例如一些统计类的请求。
- DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务。在HBase中,如果新任务比老任务更重要,且对数据一致性要求不是特别严格,可使用此策略。例如,一些实时性较高的监控数据请求可以采用这种策略,优先处理新的监控数据请求。
- AbortPolicy:默认的拒绝策略,当线程池无法处理新任务(队列已满且达到最大线程数)时,会抛出