本文基于版本为 1.8.0_281 的 JDK 对 ThreadPoolExecutor 的源码进行分析
不知道比较新的 JDK 内 ThreadPoolExecutor 内实现咋样的,应该大差不差吧,研究好主流的 JDK 1.8 一通百通。
全局概览
先来说下使用线程池的好处:
- 核心线程可以复用,省去创建线程的开销
- 通过合理的参数控制线程的数量,可以防止服务器资源消耗过多
- 提供了大量统计相关的方法,方便对线程池进行管理
再来说下大概的流程:
- 线程总数量 < corePoolSize,总会创建一个新的核心线程执行任务
- 线程总数量 >= corePoolSize 时,新来的线程任务进入任务队列中等待,然后空闲的核心线程去队列中取任务执行
- 当队列满了后,再来新的任务时启动非核心线程去执行任务
- 队列也满了,总线程数达到 maximumPoolSize,采取聚集策略进行处理
构造方法
TODO
使用
自定义线程池
先来说下为什么要自定义线程池?
一种情况是自带那几种线程池的有问题,具体有哪些问题呢?下面我们一个一个来说:
- CachedThreadPool:创建了一个「最大线程数为 Integer.MAX_VULUE 」的线程池,如果线程任务耗时并且大量创建,会导致 OOM;
- FixedThreadPool:创建了一个固定大小可重复的线程池,队列是
LinkedBlockingQueue
无界阻塞队列(因为其默认大小是 Integer.MAX_VALUE),随着任务越来越多,队列中的任务也越来越多,可能会导致 OOM; - SingleThreadExecutor:创建了一个只有一个线程的线程池,队列是
LinkedBlockingQueue
无界阻塞队列(因为其默认大小是 Integer.MAX_VALUE),随着任务越来越多,队列中的任务也越来越多,可能会耗尽 CPU 和内存资源; - ScheduledThreadPool:创建了一个能够定时执行任务或者在给定延迟后执行任务的线程池,队列用了
DelayQueue
,也是个无界的,如果使用不恰当,也会 OOM。
一种情况是给定的虽然在自己的业务场景下不会出问题,但是不满足我们的使用,需要我们进行自定义:
- 我们可以通过实现
RegectedExecutionHandler
接口来自定义策略; - 对线程池中的线程设置更有标识作用的名字。
结合 Spring
TODO
管理方法
有了下述方法后,就可以直接使用相应的方法对线程池进行监控,当然也可以重新写一个自己的监控类 extends ThreadPoolExecutor
,对某些方法的执行前后进行监控(比如启动、执行、关闭)。
核心线程
- getCorePoolSize():获取核心线程数
- setCorePoolSize():重新设置线程池的核心线程数
- prestartCoreThread():预启动一个核心线程,当且仅当工作线程数量小于核心线程数量
- prestartAllCoreThreads():预启动所有核心线程
线程池容量
- getMaximumPoolSize():获取线程池容量
- setMaximumPoolSize():重新设置线程池的最大容量
线程存活
- setKeepAliveTime():设置空闲工作线程的存活周期
- getKeepAliveTime():获取空闲工作线程的存活周期
线程队列
- purge():移除任务队列中所有是Future类型并且已经处于Cancelled状态的任务
- remove():从任务队列中移除指定的任务
- BlockingQueue getQueue():获取任务队列的引用
其他
- getTaskCount():获取所有已经被执行的任务总数的近似值
- getCompletedTaskCount():获取所有已经执行完成的任务总数的近似值
- getLargestPoolSize():获取线程池的峰值线程数(最大池容量)
- getActiveCount():获取所有活跃线程总数(正在执行任务的工作线程)的近似值
- getPoolSize():获取工作线程集合的容量(当前线程池中的总工作线程数)
关联阅读
线程池运行源码分析
ThreadPoolExecutor 关闭源码分析
推荐阅读
Java线程池实现原理及其在美团业务中的实践