博客文章地址:Java 线程池原理分析
个人博客主页:www.samsa-blog.top 欢迎各位掘友交流
引用1:tech.meituan.com/2020/04/02/... 作者: 致远 陆晨
引用2:《Java 并发编程之美》 作者: 霍陆续 薛宾田
引用3:www.cnblogs.com/HuiShouGuoQ... 作者: 小窝蜗
关于为什么使用线程池,以及它的好处,优势就不做过多介绍了。
核心类ThreadPoolExecutor继承了AbtractExecutorService,再往上,顶层抽象接口线程池状态和提交任务方法 ExecutorService,Executor。
Worker 继承 AQS Runnable 接口是具体承载任务的对象。
DefaultThreadFactory 线程工厂。
Executors 其实是个工具类里面提供了一些静态方法,这些方法根据用选择返回不同的线程池类型。
线程池在内部实际上构建了一个生产者------消费者模型 ,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转。
其运行机制如下图所示:
一、线程池信息介绍
1.1 线程池状态
java
// (高3位)用来表示线程池状态, (低29位)用来表示线程个数
// 默认是RUNNING状态, 线程个数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取高3位(运行状态)
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取低29位(线程个数)
private static int workerCountOf(int c) { return c & CAPACITY; }
// 计算ctl新值(线斗呈状态与线程个数
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池状态含义如下:
- RUNNING:接受新任务并且处理阻塞队列里的任务;
- SHUTDOWN:拒绝新任务但是处理阻队列里的任务;
- STOP:拒绝新任务并且抛弃阻塞队列的任务,同时会中断正在处理的任务;
- TIDYING:所有任务都执行完(包含阻塞队列里面的任务)后当前线程池活动线程数为0,将要调用 terminated 方法;
- TERMINATED:终止状态 terminated 方法调用完成以后的状态。
线程池状态转换图:
1.2 线程池参数
-
corePoolSize:线程池核心线程数;
-
workQueue:用于保存待执行的任务的阻塞队列;
-
maximunPoolSize:线程池最大线程数量;
-
ThreadFactory:创建线程的线程工厂;
-
RejectedExecutionHandler :拒绝策略;当队列满并且线程个数达到maximunPoolSize后采取策略。
-
keeyAliveTime :存活时间;如果当前线程池中的线程数量比核心线程数多,并且是闲置状态,则这些闲置的线程能存活的最大时间为keeyAliveTime;
-
TimeUnit:存活时间的时间单位。
1.3 线程池类型
在Java中,ExecutorService接口提供了多种线程池类型,可以根据不同的场景选择适合的线程池类型。以下是常见的几种线程池类型及其适用场景:
-
FixedThreadPool(固定大小线程池):
- 适用于执行长期任务,控制线程的最大并发数,可以保证线程池中的线程数量始终不变。
- 适用于服务器端需要控制并发线程数量的场景,如:Web服务器。
-
CachedThreadPool(缓存线程池):
- 适用于执行大量短期异步任务的场景,线程池根据需要自动创建新线程,没有任务时会回收空闲线程。
- 适用于执行很多短期异步任务的小程序或者负载较轻的服务器。
-
SingleThreadExecutor(单线程线程池):
- 只会创建一个单线程来执行任务,保证所有任务按顺序执行。
- 适用于需要保证任务按照顺序执行的场景,如:事件循环、数据库连接池等。
-
ScheduledThreadPool(定时任务线程池):
- 适用于需要定时执行任务或延迟执行任务的场景。
- 可以指定线程池大小,可以执行定时任务和周期性任务。
-
WorkStealingPool(工作窃取线程池):
-
Java 8引入的一种线程池,基于ForkJoinPool实现,用于执行耗时较长的任务。
-
可以充分利用多核处理器的优势,提高任务执行效率。
-
-
newSingleThreadExecutor:
创建一个核心线程个数和最大线程都为
1
的线程池并且阻塞队列长度为Integer.MAX_VALUE
。keepAliveTime=0
说明只要线程个数比核心线程个数多并且当前空闲回收。javapublic static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
-
newFixedThreadPool:
创建一个核心线程个数和最大线程个数都为
nThreads
的线程池,并且阻塞队列长度为Integer.MAX_VALUE
。keepAliveTime=0
说明只要线程个数比核心线程个数多并且当前空闲回收。javapublic static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
-
newCachedThreadPool:
创建一个按需创建线程的线程池,初始线程个数为0,最多线程个数为
Integer.MAX_VALUE
,并且阻塞队列为同步队列;keepAliveTime=60
说明只要当前线程在60s内空闲则回收。这个类型的特殊之处在于加入同步队列任务会被马上执行,同步队列里面最多只有一个任务。
javapublic static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
二、任务执行流程
java.util.concurrent.ThreadPoolExecutor#execute
这里其实就是任务提交给线程池之后,任务调度的过程。
java
public class ThreadPoolExecutor extends AbstractExecutorService {
public void execute(Runnable command) {
// (1)如果任务为null ,则抛出NPE异常
if (command == null)
throw new NullPointerException();
// (2)获取当前线程池的状态+线程个数变量的组合值
int c = ctl.get();
// (3)当前线程池中线程个数是否小于corePoolSize,小于则开启新线程运行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// (4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
// (4.1)二次检查
int recheck = ctl.get();
// (4.2)如果当前线程池状态不是RUNNING则从队列中删除任务,并执行拒绝策略
if (!isRunning(recheck) && remove(command))
reject(command);
// (4.3)否则如果当前线程池为空 则添加一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// (5)如果队列满,则新增线程,新增失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
}
代码执行步骤对应流程图:
三、addWorker方法
在主流程的addWorker
方法主要做两件事情:
- 第一部分双重循环的目的是通过 CAS 操作增加线程数;
- 第二部分主要是把并发安全的任务加到 workers 里面,并且启动任务执行。
java
private boolean addWorker(Runnable firstTask, boolean core) {
// 第一部分:双重循环的目的是通过 CAS 操作增加线程数;
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// (6)检查队列是否只在必要时为空
// 下面条件等价于:
// rs >= SHUTD8WN &&
// (rs !=SHUTDOWN (Ⅰ) 当前线程池状态为:STOP、TIDYING或TERMINATED
// || firstTask != null (Ⅱ) 当前线程池状态为 SHUTDOWN 并且己经有了第一个任务
// || workQueue.isEmpty()) (Ⅲ) 当前线程池状态为 SHUTDOWN 并且任务队列为空
if (rs >= SHUTDOWN && !(rs == SHUTDOWN
&& firstTask == null
&& !workQueue.isEmpty()))
return false;
// (7)循环CAS增加线程个数
for (;;) {
int wc = workerCountOf(c);
// (7.1)如果线程个数超过最大线程数,则返回 false
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// (7.2)CAS增加线程个数,同时只有一个线程成功
if (compareAndIncrementWorkerCount(c))
break retry;
// (7.3)CAS 失败了,则看线程池状态是否变化了,变化则跳到外层循环重新尝试获取线程池状态,如果没有变化则还在内层循环重新CAS
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 第二部分:主要是把并发安全的任务加到 workers 里面,并且启动任务执行。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
// (8)到这里说明 CAS成功了
try {
// (8.1)创建worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// (8.2)加独占锁,为了实现workers同步,因为可能多个线程调用了线程池的execute方法
mainLock.lock();
try {
// (8.3)重新检查线程池状态,以避免在获取锁前调用了shutdown接口
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// (8.4)添加工作线程
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// (8.5)添加成功后则启动任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker方法执行流程图:
四、工作线程Worker
在第三小节中,代码8.1:为任务构建Worker(工作线程),代码8.4:添加Worker到工作线程集合中,代码8.5:启动线程。
对8.1分析:w = new Worker(firstTask);
java
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
// (8.1)
Worker(Runnable firstTask) {
// 在构造函数内首先设置 Worker 的状态为 -1,这是为了避免当前 Worker 在调用
// runWorker方法前被中断(当其他线程调用了线程池的 shutdownNow 时,如果 Worker
// 状态 >=0,则会中断该线程)。
// 这里设置了线程的状态为 -1 ,所以该线程就不会被中断了。
setState(-1); // 在运行Worker之前禁止中断
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 创建一个线程
}
// (8.5) 启动线程就会来到这里,最终进入到runWorker方法
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // (9)state设置为0, 允许中断
boolean completedAbruptly = true;
try {
// (10)task就是firstTask,如果不为空,就先执行firstTask;如果为空,就调用getTask(),从阻塞队列中获取任务。
while (task != null || (task = getTask()) != null) {
// (10.1)
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
try {
// (10.2)执行任务前干一些事情
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); // (10.3)执行任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown); // (10.4)执行任务完毕后干一些事情
}
} finally {
task = null;
// (10.5) 统计当前Worker(工作线程)完成了多少个任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// (11)执行清理工作
// 来到这里,说明 当前不论是firstTask,还是阻塞队列中workQueue已经没有任务了。
// 如果当前的工作线程由于抛出用户异常被终结,那么会新创建一个非核心线程。
// 如果当前的工作线程并不是抛出用户异常被终结(正常情况下的终结),那么会这样处理:
// 第一种:allowCoreThreadTimeOut为true,也就是允许核心线程超时的前提下,
// 如果任务队列空,则会通过创建一个非核心线程保持线程池中至少有一个工作线程。
// 第二种:allowCoreThreadTimeOut为false,如果工作线程总数大于corePoolSize则直接返回,
// 否则创建一个非核心线程,也就是会趋向于保持线程池中的工作线程数量趋向于corePoolSize。
// processWorkerExit()执行完毕之后,意味着该工作线程的生命周期已经完结。
processWorkerExit(w, completedAbruptly);
}
}
}
Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。
thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务。
firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行阻塞队列(workQueue)中的任务,也就是非核心线程的创建。
-
其实对应的就是 代码(10)的执行逻辑:
-
Worker.runWorker方法执行流程图:
-
processWorkerExit(w, completedAbruptly)
javaprivate void processWorkerExit(Worker w, boolean completedAbruptly) { // 如果没有任何异常抛出的情况下是通过getTask()返回null引导线程正常跳出runWorker()方法的while死循环 // 从而正常终结,这种情况下在getTask()中已经调用decrementWorkerCount()方法把线程数减1。 // 因为抛出用户异常导致线程终结,这么completedAbruptly=true,这里会使工作线程数减1。 if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 全局的已完成任务记录数 + 此将要终结的Worker中的已完成任务数 completedTaskCount += w.completedTasks; // 从工作线程集合中移除当前要销毁的Worker workers.remove(w); } finally { mainLock.unlock(); } // 用于根据当前线程池的状态判断是否需要进行线程池terminate处理 tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { // 没有异常的情况下completedAbruptly=false,这里会进入到if // 如果允许核心线程超时allowCoreThreadTimeOut=true,则线程数最小值为0,否则最小值为corePoolSize int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 如果最小值为0,同时任务队列不空,则更新最小值为1 if (min == 0 && ! workQueue.isEmpty()) min = 1; // 如果工作线程数大于等于最小值,直接返回,不新增非核心线程 if (workerCountOf(c) >= min) return; // replacement not needed } // 新增工作线程 addWorker(null, false); } }
- processWorkerExit方法大致做的事情:
- 从工作线程集合中移除当前要销毁的Worker。
- 这里的销毁,只是销毁了对线程的引用。
- 因为线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。
- 如果当前的工作线程由于抛出用户异常被终结,那么会新创建一个非核心线程。
- 如果当前的工作线程并不是抛出用户异常被终结(正常情况下的终结),那么会这样处理:
- 第一种:allowCoreThreadTimeOut为true,也就是允许核心线程超时的前提下,如果任务队列空,则会通过创建一个非核心线程保持线程池中至少有一个工作线程。
- 第二种:allowCoreThreadTimeOut为false,如果工作线程总数大于corePoolSize则直接返回,否则创建一个非核心线程,也就是会趋向于保持线程池中的工作线程数量趋向于corePoolSize。
- 从工作线程集合中移除当前要销毁的Worker。
- processWorkerExit()执行完毕之后,意味着该工作线程的生命周期已经完结。
- processWorkerExit方法大致做的事情: