前言
“池”这个字恐怕大家都耳熟能详,诸如连接池、对象池以及线程池等等。我们可以将暂时用不到的资源搁置在池中,等待需要使用的时候,就可以直接从池中获取。池不仅可以重复利用资源,而无需重新进行资源的构建工作,还可以因此简化资源的管理与调用,提升效率与简化编码。
而线程池,就是一种常见的“池”。比起降低新建线程的开销,线程池更可以帮助我们处理线程的切换,减少线程上下文切换的开销。同时,也可以帮助任务的调度与管理。
导航
Executor
在了解线程池 ThreadPoolExecutor 之前,我们先来看一下它实现的顶级接口——Executor:执行器。
1 | public interface Executor { |
Executor 可以说是非常简单了,只有一个 execute 方法。使用过线程池的读者们对这个方法大概很熟悉,那么 Executor 接口是用来做什么的?这是文档上的解释:
这个接口解耦了任务的提交与执行。
线程池无疑解耦了任务的提交与执行,所以实现了 Executor。而 Executor 接口下,并不止线程池这一部分。
顺着上图,我们也就可以理解在 java 中,线程池的实现类属于一种执行器,所以,线程池才被命名为 ThreadPoolExecutor。
ThreadPoolExecutor
这是我们所熟知的线程池实现类,先上示例代码:
1 | class NetworkService implements Runnable { |
示例代码往往展现了最基础的功能。由上述代码,我们可以看到,线程池的关键步骤有两个:
- 线程池的创建
- 任务的提交
线程池的创建
Executors.newFixedThreadPool(poolSize) 方法究竟做了什么呢?
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
emmm,参数有点多。这是完整参数版的构造方法:
1 | public ThreadPoolExecutor(int corePoolSize, |
在介绍这些参数的含义之前,我们约定一些名词:
- workers:工作线程,也就是执行任务的线程,以下简称线程
- alive:线程存活,也就是线程没有被终止
那么,这几个参数的意义是:
- corePoolSize:核心线程数,允许长期空闲的线程数量
- maximumPoolSize:最大线程数量
- keepAliveTime:超过 corePoolSize 的空闲线程存活时间
- RejectedExecutionHandler:拒绝执行处理器,当任务被拒绝执行时触发
- workQueue:暂存任务的工作队列,下称队列
- threadFactory: 创建线程的线程工厂
要了解这些参数的作用,还得先清除线程池的执行流程。因此,我们再来看另一关键步骤:execute。
任务的提交
1 | public void execute(Runnable command) { |
因此,线层执行的三个步骤是非常清楚的:
了解了大体流程,我们再来关注 execute 方法的细节。在笔者看来,execute 方法有着两个关键点:ctl 与 worker。下面,我们就来这两个对象。
ctl
java 中有不少使用一个字段来控制/记录对象状态的,ctl 就是其一。线程池一共有 5 中状态,分别是:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。这些状态的表示如下:
1 | private static final int RUNNING = -1 << COUNT_BITS; // 接受新任务并且处理任务 |
读者们可能已经意识到了,这些状态是有递增顺序的,并且与值的大小相对应。因此,这些协助判断状态的方法也不难理解了:
1 | private static boolean runStateLessThan(int c, int s) { |
对线程池的状态有了初步的了解,我们再来看 ctl。ctl 是 AtomicInteger 类型,value 是 32 位的 int。ctl 用最高 3 位表示线程池状态(2^3=8>5),低 29 位表示线程数量。因此,相关的方法如下:
1 | private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); |
worker
worker 继承了 AQS,通过锁来防止线程正在运行时被中断。而我们优先的关注点,自然在线程执行的 run()方法上:
1 | private final class Worker extends AbstractQueuedSynchronizer |