面试题答案
一键面试Java线程池工作原理
- 线程池创建:通过
ThreadPoolExecutor
类来创建线程池,其构造函数包含核心线程数(corePoolSize
)、最大线程数(maximumPoolSize
)、存活时间(keepAliveTime
)、时间单位(unit
)、任务队列(workQueue
)以及线程工厂(threadFactory
)和拒绝策略(RejectedExecutionHandler
)等参数。 - 任务提交:当提交一个任务到线程池时,线程池会按以下顺序处理:
- 首先判断核心线程数是否已满,如果未满,则创建新的核心线程来执行任务。
- 如果核心线程数已满,任务会被放入任务队列(
workQueue
)中等待执行。 - 如果任务队列也已满,此时判断线程数是否达到最大线程数,如果未达到,则创建非核心线程来执行任务。
- 如果线程数达到最大线程数且任务队列已满,根据设置的拒绝策略来处理该任务。
- 线程复用:线程池中的线程执行完任务后不会立即销毁,而是回到线程池中等待下一个任务,从而实现线程的复用,减少线程创建和销毁的开销。
- 线程回收:非核心线程如果在指定的存活时间(
keepAliveTime
)内没有任务可执行,会被回收。
创建参数对性能的影响
- 核心线程数(
corePoolSize
):- 过小:导致任务等待,响应时间变长,系统吞吐量降低,因为任务需要等待核心线程空闲。
- 过大:会占用过多资源,可能导致系统负载过高,特别是在任务执行时间较长且并发任务数较少的情况下,过多的核心线程会浪费系统资源。
- 最大线程数(
maximumPoolSize
):- 过小:在高并发场景下,任务队列容易溢出,触发拒绝策略,导致任务无法执行。
- 过大:会占用过多系统资源,如内存等,而且过多的线程切换也会增加系统开销,降低性能。
- 队列容量:
- 过小:任务队列很快就会满,导致更多的线程被创建直到达到最大线程数,增加系统资源消耗。
- 过大:任务长时间在队列中等待,可能导致任务响应时间变长,而且队列占用过多内存。
- 存活时间(
keepAliveTime
)和时间单位(unit
):- 存活时间过长:非核心线程长时间不被回收,占用系统资源。
- 存活时间过短:可能导致线程频繁创建和销毁,增加系统开销。
高并发场景下合理配置线程池参数
- 分析任务类型:
- CPU密集型任务:核心线程数一般设置为
CPU核心数 + 1
,因为CPU密集型任务主要消耗CPU资源,过多的线程会导致线程切换开销增大。例如,一个4核心的CPU,核心线程数设置为5。 - I/O密集型任务:核心线程数可以设置为
2 * CPU核心数
,因为I/O操作会使线程等待,需要更多线程来充分利用CPU资源。比如4核心CPU,核心线程数可设为8。
- CPU密集型任务:核心线程数一般设置为
- 考虑系统资源:要根据服务器的内存、CPU等资源来调整最大线程数和队列容量,避免资源耗尽。
- 动态调整:可以使用
ScheduledThreadPoolExecutor
定期监测系统负载,动态调整线程池参数。
简单任务处理场景示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池,核心线程数和最大线程数都为5
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
// 模拟任务执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskNumber + " completed");
});
}
// 关闭线程池
executorService.shutdown();
}
}
在上述示例中,使用Executors.newFixedThreadPool(5)
创建了一个固定大小为5的线程池,提交10个任务到线程池中执行,每个任务模拟执行1秒。最后调用executorService.shutdown()
关闭线程池。