面试题答案
一键面试创建自定义线程池并应用到CompletableFuture异步任务执行
- 创建自定义线程池:
- 使用
ThreadPoolExecutor
类来创建自定义线程池。例如:
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // 核心线程数 20, // 最大线程数 1, TimeUnit.MINUTES, // 线程存活时间 new LinkedBlockingQueue<>(100) // 任务队列 );
- 核心线程数
corePoolSize
表示线程池中一直存活的线程数量,即使它们处于空闲状态。 - 最大线程数
maximumPoolSize
表示线程池允许创建的最大线程数。 - 线程存活时间
keepAliveTime
和时间单位unit
定义了非核心线程在空闲状态下可以存活的时长,超过这个时间,非核心线程会被销毁。 - 任务队列
workQueue
用于存放提交但尚未执行的任务。
- 使用
- 将自定义线程池应用到CompletableFuture异步任务执行:
- CompletableFuture提供了
supplyAsync
和runAsync
等方法来执行异步任务,这些方法都有重载形式,可以接受一个Executor
参数。 - 示例:
CompletableFuture.supplyAsync(() -> { // 异步任务逻辑 return "result"; }, executor);
- 这样就将自定义的线程池
executor
应用到了CompletableFuture的异步任务执行中。
- CompletableFuture提供了
相较于默认配置的优势
- 资源控制更精准:
- 默认配置使用
ForkJoinPool.commonPool()
,其线程数量是动态调整的,可能无法满足特定业务场景对资源的精准需求。自定义线程池可以根据业务负载和硬件资源情况,精确设置核心线程数、最大线程数等参数,避免资源过度使用或不足。
- 默认配置使用
- 任务隔离:
- 在高并发场景下,不同类型的任务可能有不同的性能需求。使用自定义线程池可以为不同类型的任务创建独立的线程池,实现任务隔离,避免一种任务的高负载影响其他任务的执行。例如,对于I/O密集型任务和CPU密集型任务可以分别使用不同配置的线程池。
- 提升性能:
- 根据业务特点合理配置线程池参数,如调整任务队列大小,可以减少线程创建和销毁的开销,提高任务执行效率。对于高并发且任务执行时间较短的场景,适当增加核心线程数可以减少任务排队等待时间,提升整体性能。
可能遇到的问题
- 线程资源耗尽:
- 如果设置的核心线程数和最大线程数过小,而任务提交速度过快,任务队列可能会迅速填满,导致新的任务无法提交,抛出
RejectedExecutionException
异常。同时,系统可能因为线程资源不足而性能下降。
- 如果设置的核心线程数和最大线程数过小,而任务提交速度过快,任务队列可能会迅速填满,导致新的任务无法提交,抛出
- 线程池配置不合理:
- 如果核心线程数设置过大,可能会导致系统资源过度消耗,尤其是在CPU密集型任务场景下,过多的线程竞争CPU资源会降低整体性能。另外,任务队列过大可能会导致内存占用过高,甚至引发内存溢出问题。
- 任务调度复杂:
- 当使用多个自定义线程池时,任务的调度和管理变得更加复杂。需要合理分配任务到不同的线程池,否则可能无法充分发挥自定义线程池的优势,甚至导致系统性能下降。例如,如果将I/O密集型任务分配到了配置为适合CPU密集型任务的线程池,可能无法达到最优性能。