面试题答案
一键面试提升线程池任务提交性能
- 任务队列选择
- 无界队列(如
LinkedBlockingQueue
):适用于任务量较大且处理时间较短的场景。它可以容纳大量任务,不会因为队列满而拒绝新任务,但可能会导致内存占用过高,甚至OOM,因为它没有容量限制。 - 有界队列(如
ArrayBlockingQueue
):适合对资源使用较为敏感的场景。设置合理的容量可以防止任务堆积过多而耗尽内存,但如果容量设置过小,可能会频繁触发拒绝策略。 - 优先队列(如
PriorityBlockingQueue
):适用于任务有优先级之分的场景。根据任务的优先级进行排序,优先处理高优先级任务。
- 无界队列(如
- 线程池参数调优
- 核心线程数(
corePoolSize
):应根据系统的CPU核心数、I/O特性及任务处理时间来设置。对于CPU密集型任务,corePoolSize
一般设置为CPU核心数 + 1;对于I/O密集型任务,可以适当增加核心线程数,例如CPU核心数 * 2。 - 最大线程数(
maximumPoolSize
):在高并发场景下,maximumPoolSize
应根据系统资源(如内存、网络带宽等)和预估的最大并发任务数来设置。设置过大可能导致资源耗尽,设置过小则可能无法充分利用系统资源。 - 队列容量:如上述任务队列选择中提到,根据任务特性和系统资源设置合理的队列容量。
- 线程存活时间(
keepAliveTime
):对于高并发场景,如果任务提交频率较高,keepAliveTime
可以设置得较小,以避免过多线程长时间空闲占用资源;如果任务提交频率波动较大,可适当增大keepAliveTime
,减少线程频繁创建和销毁的开销。
- 核心线程数(
- 任务提交方式优化
- 批量提交:如果任务之间相互独立,可以将多个任务封装成一个批量任务提交到线程池,减少线程池调度开销。例如使用
ExecutorService.invokeAll
方法。 - 异步提交:避免在主线程中同步等待任务执行结果,使用
Future
或CompletableFuture
来异步获取任务执行结果,提高主线程的并发处理能力。
- 批量提交:如果任务之间相互独立,可以将多个任务封装成一个批量任务提交到线程池,减少线程池调度开销。例如使用
异常隔离策略
- 设计策略
- 使用
try - catch
块:在任务的run
方法或call
方法内部使用try - catch
块捕获异常,避免异常向上抛出影响其他任务。例如:
- 使用
class MyTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
try {
// 任务执行逻辑
return result;
} catch (Exception e) {
// 异常处理逻辑,记录日志等
return null;
}
}
}
- **自定义线程工厂**:通过自定义线程工厂创建线程,在`Thread.UncaughtExceptionHandler`中处理未捕获的异常。例如:
public class MyThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((thread, ex) -> {
// 异常处理逻辑,如记录日志
});
return t;
}
}
- **任务隔离**:将不同类型的任务提交到不同的线程池,当某个线程池中的任务出现异常时,不会影响其他线程池中的任务。
2. 可行性
- 易于实现:try - catch
块和自定义线程工厂的方式在代码层面实现较为简单,对现有代码侵入性较小。
- 不影响其他任务:通过捕获异常或自定义异常处理器,能够有效防止某个任务的异常传播到其他任务,保证整个线程池的稳定性。
3. 潜在风险
- 异常信息丢失:在try - catch
块中如果没有妥善记录异常信息,可能导致排查问题困难。
- 资源浪费:任务隔离方式会增加线程池的创建和管理开销,如果任务量较小,可能会造成资源浪费。