avatar

目录
JUC系列 (1) —— ThreadPool 线程池

J.U.C 系列第一篇

一. JDK 中的线程池

子曰:温故而知新。前不久翻看 Java 的基础知识,又看了美团的一篇关于线程池的文章,于是乎我尝试着梳理下有关于 JDK 中线程池这块儿内容。

关于使用线程池的好处,这里就直接罗列出来了:

  • 线程池可复用和协调多个线程,控制最大并发数

  • 线程池实现了任务队列的缓存策略和拒绝机制

  • 线程池可实现某些特点的功能,比如定时执行,周期执行等

  • 隔离线程环境

JDK 1.5 以后, Doug Lea 大神为 JDK 贡献了 JUC 包, 使得 Java 世界的并发编程迈上了一个新台阶。JUC 中提供的 Executors 框架,本身默认就提供了好几种线程池的实现,分别是:

1.1 Executors.newFixedThreadPool
java
1
2
3
4
5
6
// nThreads 固定线程数大小,既是核心线程数,也是最大线程数,无空闲线程 
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
1.2 Executors.newCachedThreadPool
java
1
2
3
4
5
6
7
8
/** 最大线程数可达 Integer.MAX_VALUE, 高度可伸缩,由于线程池可以近似无限增长,存在 OOM 风险
** keepAliveTime 默认60s,工作线程空闲时将被回收
**/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
1.3 Executors.newScheduledThreadPool
java
1
2
3
4
5
6
7
8
/**
** 同 cachedThreadPool 一样最大线程数可达 Integer.MAX_VALUE, 故同样有 OOM 风险,
** 支持定时及周期性执行任务,相比 Timer 之流功能更强大,不回收工作线程
**/
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
1.4 Executors.newSingleThreadPool
java
1
2
3
4
5
6
7
8
9
/**
** 单线程的线程池,串行执行所有任务
**/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
1.5 Executors.newWorkStealingPool
java
1
2
3
4
5
6
7
8
9
10
 /**
** JDK 1.8 引入,创建持有足够线程的线程池来支持给定的并发度,另有构造函数 ExecutorService newWorkStealingPool(int parallelism)
** 通过使用多个队列来减少竞争
**/
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}

这五大核心构造函数,是 Executors 框架自带的 API. 其底层很多都依赖于 ThreadPoolExecutor 这个类。在阿里巴巴出版的 《Java开发手册》中是这样写的:

【强制】 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

强制开发人员直接使用 这个类来创建线程池,因为 Executors 框架提供的默认实现内部很多都采用了无解队列,且允许创建的最大线程数为 Integer.MAX_VALUE, 因此存在着资源耗尽的风险。

二. ThreadPoolExecutor

2.1 构造函数参数

从上面的代码也可以看到, ThreadPoolExecutor 的构造函数需要的参数还不少。摘录一个最全参数构造函数的源码如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

具体参数为:

  1. corePoolSize: 常驻核心线程数,一般会设置成大于0的整数,即使是空闲时间,也不会销毁,该值设置相当重要,需结合系统的 CPU 核心数目综合考量设置
  2. maximumPoolSize: 表示线程池能够容纳同时执行的最大线程数,超出 corePoolSize 部分的线程数,在空闲时间到达 keepAliveTime 之后,将会被销毁
  3. keepAliveTime: 表示线程池中超过coreSizePool 数目的其他线程允许空闲的最长时间,超过即销毁线程,直到线程池中的个数恢复为 corePoolSize.
  4. timeUnit: 时间单位,keepAliveTime 参数的时间单位,通常为 TimeUnit.SECONDS
  5. workQueue: 缓存任务队列,当请求的线程数大于 corePoolSize 时,任务会进入 BlockingQueue 阻塞队列,在 BlockingQueue 队列满了之后,如果还有新任务要处理,才有依据 maximumPoolSize 参数的设置新建线程。
  6. threadFactory: 线程工厂。用来生产一组相同任务的线程, 通常用来给线程池命名,方便排查问题。
  7. 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 的内容。


(本文完)

文章作者: JanGin
文章链接: http://jangin.github.io/2021/08/15/juc-jdk-threadpool/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JanGin's BLOG

评论