J.U.C 系列第一篇
一. JDK 中的线程池
子曰:温故而知新。前不久翻看 Java 的基础知识,又看了美团的一篇关于线程池的文章,于是乎我尝试着梳理下有关于 JDK 中线程池这块儿内容。
关于使用线程池的好处,这里就直接罗列出来了:
线程池可复用和协调多个线程,控制最大并发数
线程池实现了任务队列的缓存策略和拒绝机制
线程池可实现某些特点的功能,比如定时执行,周期执行等
隔离线程环境
JDK 1.5
以后, Doug Lea 大神为 JDK 贡献了 JUC
包, 使得 Java 世界的并发编程迈上了一个新台阶。JUC
中提供的 Executors 框架,本身默认就提供了好几种线程池的实现,分别是:
1.1 Executors.newFixedThreadPool
1 | // nThreads 固定线程数大小,既是核心线程数,也是最大线程数,无空闲线程 |
1.2 Executors.newCachedThreadPool
1 | /** 最大线程数可达 Integer.MAX_VALUE, 高度可伸缩,由于线程池可以近似无限增长,存在 OOM 风险 |
1.3 Executors.newScheduledThreadPool
1 | /** |
1.4 Executors.newSingleThreadPool
1 | /** |
1.5 Executors.newWorkStealingPool
1 | /** |
这五大核心构造函数,是 Executors 框架自带的 API. 其底层很多都依赖于 ThreadPoolExecutor 这个类。在阿里巴巴出版的 《Java开发手册》中是这样写的:
【强制】 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
强制开发人员直接使用 这个类来创建线程池,因为 Executors 框架提供的默认实现内部很多都采用了无解队列,且允许创建的最大线程数为 Integer.MAX_VALUE
, 因此存在着资源耗尽的风险。
二. ThreadPoolExecutor
2.1 构造函数参数
从上面的代码也可以看到, ThreadPoolExecutor 的构造函数需要的参数还不少。摘录一个最全参数构造函数的源码如下:
1 | public ThreadPoolExecutor(int corePoolSize, |
具体参数为:
- corePoolSize: 常驻核心线程数,一般会设置成大于0的整数,即使是空闲时间,也不会销毁,该值设置相当重要,需结合系统的 CPU 核心数目综合考量设置
- maximumPoolSize: 表示线程池能够容纳同时执行的最大线程数,超出 corePoolSize 部分的线程数,在空闲时间到达 keepAliveTime 之后,将会被销毁
- keepAliveTime: 表示线程池中超过coreSizePool 数目的其他线程允许空闲的最长时间,超过即销毁线程,直到线程池中的个数恢复为 corePoolSize.
- timeUnit: 时间单位,keepAliveTime 参数的时间单位,通常为 TimeUnit.SECONDS
- workQueue: 缓存任务队列,当请求的线程数大于 corePoolSize 时,任务会进入
BlockingQueue
阻塞队列,在BlockingQueue
队列满了之后,如果还有新任务要处理,才有依据 maximumPoolSize 参数的设置新建线程。 - threadFactory: 线程工厂。用来生产一组相同任务的线程, 通常用来给线程池命名,方便排查问题。
- handler: 拒绝策略执行器。当请求的线程数超过 maximumPoolSize 时,依据这个参数的设置采取接下来对应的任务请求处理策略。对应的拒绝策略 ThreadPoolExecutor 类中提供了四种实现:
- AbortPolicy (默认): 丢弃任务并抛出异常 RejectedExecutionException
- DiscardPolicy : 丢弃任务,不抛出异常
- DiscardOldestPolicy:丢弃任务队列中等待最久的任务
- CallerRunsPolicy:调用任务的 run() 方法绕过线程池来执行
2.2 线程池状态
ThreadPoolExecutor
中采用一个 32bit 的 volatile 变量 ctl 的高3位来表征线程池的状态:
状态 | 高3位 | 接收新任务 | 处理队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | YES | YES | |
SHUTDOWN | 000 | NO | YES | 不会接收新任务,但会处理阻塞队列中的剩余任务 |
STOP | 001 | NO | NO | 中断正在执行的任务,抛弃阻塞队列任务 |
TIDYING | 010 | 任务全部执行完毕,活动线程为0,即将进入TERMINATED | ||
TERMINATED | 011 | 终结状态 |
三. ThreadPoolExecutor 源码详解
其实网上关于源码这方面的详细解读文章可谓是汗牛充栋,在这里我找了一篇很有代表性的文章,个人觉得这篇写得真的很详细,鄙人恐难以望其项背。贴在下面:
深入理解Java线程池:ThreadPoolExecutor
另外,也可参阅《码出高效——Java开发手册》中 P246~P251 的内容。
(本文完)