Java线程池那点事(面试高频)
1. 线程的生命周期
NEW -> RUNNABLE -> BLOCKED -> WAITING -> TIMED_WAIT -> TERMINATED
2. 线程池参数
- int corePoolSize -- 核心线程数
- int maximumPoolSize -- 最大线程数
- long keepAliveTime -- 非核心线程探活时间
- TimeUnit unit -- 非核心线程探活时间单位
- BlockingQueue<Runnable> workQueue -- 阻塞队列
- RejectedExecutionHandler handler -- 拒绝策略
3. 线程池的工作原理
- 任务到达时先判断是否有空闲线程,如果有则使用空闲线程。
- 如果没有空闲线程,则判断线程数是否大于核心线程数,如果小于核心线程数则创建新的线程。
- 如果大于核心线程数,则将任务加入到阻塞队列。
- 当阻塞队列满时,则创建非核心线程,如果线程数大于等于最大线程数,则走拒绝策略。
- 当一定时间没有任务后,非核心线程就会自动销毁。
4. 如何手动启动核心线程
- prestartAllCoreThreads():启动所有核心线程并返回启动的线程数。
- prestartCoreThread():启动一个线程,等待任务。如果已达到核心线程数,这个方法返回 false,否则返回 true。
5. 线程池拒绝策略
- 丢弃任务,抛异常(默认)。
- 丢弃任务,不抛异常。
- 丢弃队列前面的任务(最旧的任务),从新提交被拒绝的任务。
- 由调用线程来执行。
6. 创建线程池需要注意哪些点
- 设置合理的核心线程数以及最大线程数,避免OOM。
- 设置阻塞队列的长度,避免OOM。
- 自定义线程工厂,为创建的线程设置名称。
- 设置合理的线程活跃时间以及时间单位,有利于非核心线程的回收。
- 选择合适的拒绝策略,默认抛出RejectedExecutionexception类型的Runtimeeexception。(队列满了,线程数已经达到了最大线程数)。
- 核心线程数建议设置为CPU核数,最大线程数建议设置为CPU核数*2(需要根据是否IO操作密集来决定)。
7. 如何给线程池创建的线程设置名称
// 创建自定义的ThreadFactory
ThreadFactory customThreadFactory = newCustomThreadFactory("MyThreadPool");// 使用自定义的ThreadFactory创建线程池
ExecutorService executor = newThreadPoolExecutor( 2, 4, 10, TimeUnit.SECONDS, newArrayBlockingQueue<>(2), customThreadFactory);
8. 线程池如何知道线程任务是否执行完
- 通过 isTerminated() 判断线程池内是否还有线程在运行。
- 通过 submit() 方法提供的 Feture 返回值的 get()。
- 通过 CountDownLatch 计数器,设置计数值为 1,在代码块后面用 await() 阻塞,当计数值为 0 时,await() 会放行。
- 使用 shutdown() 和 awaitTermination()。shutdown() 关闭线程池不在接受新任务,awaitTermination() 等待所有任务执行完成。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class ThreadPoolExample {public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(10);// 提交一些任务for (int i = 0; i < 10; i++) {executorService.submit(() -> {// 模拟任务处理System.out.println("任务开始处理:" + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("任务处理完成:" + Thread.currentThread().getName());});}// 关闭线程池,不再接受新任务executorService.shutdown();// 等待所有任务完成if (executorService.awaitTermination(60, TimeUnit.SECONDS)) {System.out.println("所有任务都已处理完成");} else {System.out.println("有任务未能在指定时间内完成");}}
}
9. Executors线程池
- Executors.newCachedThreadPool(),缓存池。核心线程:0,最大线程:int.max,活跃时间:60s,队列:同步队列(SynchronousQueue,放入一个元素时,必须有一个线程准备好接收该元素,否则插入操作将阻塞,直到有线程来接收)。
- Executors.newFixedThreadPool(10),固定大小线程池。核心线程:10,最大线程:10,活跃时间:0s,队列:阻塞队列。
- Executors.newScheduledThreadPool(5),任务调度池。核心线程:5,最大线程:int.max,活跃时间:10ms,队列:延迟队列(DelayedWorkQueue)。
- Executors.newSingleThreadExecutor(),单线程池。核心线程:1,最大线程:1,活跃时间:0s,队列:阻塞队列。
- Executors.newWorkStealingPool(),工作窃取线程池,通过ForkJoinPool实现的。
10. 如何合理使用线程池
10.1 使用自定义线程池
new ThreadPoolExecutor(coreThreads, poolSize, 10, TimeUnit.SECONDS, state.threadPoolQueue, new ThreadPoolExecutor.AbortPolicy());
10.2 核心线程数、最大线程数、阻塞队列大小设置
10.2.1 CPU密集型
- 核心线程数:CPU核数 + 1。
- 最大线程数:核心线程数的 2 - 3 倍。
- 队列大小:需要考虑任务的到达速率(任务产生的频率)、任务的执行时间和系统资源。如果系统资源有限或者任务不能长时间等待,可以设置较小的队列。反之任务到达速率较快,可以长时间执行,且系统资源充足,可以设置较大的队列。旺店通任务队列大小100,阻塞队列使用的是ArrayBlockingQueue。
10.2.2 IO密集型
- 核心线程数:CPU核数 * 2。
- 最大线程数:核心线程数的 2 - 3 倍。
- 队列大小:需要考虑任务的到达速率(任务产生的频率)、任务的执行时间和系统资源。如果系统资源有限或者任务不能长时间等待,可以设置较小的队列。反之任务到达速率较快,可以长时间执行,且系统资源充足,可以设置较大的队列。旺店通任务队列大小100,阻塞队列使用的是ArrayBlockingQueue。