Categories
程式開發

JUC 之ThreadPoolExecutor實現原理分析


ThreadPoolExecutor工作流程

JDK1.5中引入了線程池,合理地利用線程池能有效的提高程序的運行效率,但不當的使用線程池也會帶來致命的危害。作為使用最多的ThreadPoolExecutor,很有必要深入理解的其源碼與實現原理。

先看一下ThreadPoolExecutor是如何工作的,暫時不看源碼,這樣會先有一個比較直觀的印像有利於後面深入分析源碼。

既然是線程池那麼提交任務後一定要創建線程用於執行任務,ThreadPoolExecutor創建線程執行提交任務的流程如下。

JUC 之ThreadPoolExecutor實現原理分析 1

簡單介紹一下,一個任務提交給線程池後,線程池創建線程來執行提交任務的流程。

當提交任務時線程池中的來用執行任務的線程數小於corePoolSize(核心線程數),則線程池利用ThreadFacory(線程工廠)創建線程用於執行提交的任務。否則執行第二2步。當提交任務時線程池中的來用執行任務的線程數大於corePoolSize(核心線程數),但workQueue沒有滿,則線程池會將提交的任務先保存在workQueue(工作隊列),等待線程池中的線程執行完其它已提交任務後會循環從workQueue中取出任務執行。否則執行第3步。當提交任務時線程池中的來用執行任務大於corePoolSize(核心線程數),且workQueu已滿,但沒有超過maximunPoolSize(最大線程數),則線程池利用ThreadFacory(線程工廠)創建線程用於執行提交的任務。否則執行4。當提交任務時線程池中的來用執行任務大於maximunPoolSize,執行線程池中配置的拒絕策略(RejectedExecutionHanlder)。

下圖給出了ThreadPoolExecutor更加直觀的整體運行圖。圖中標註1、2、3、4的分別對應上面分析中的第1、第2、第3、第4步。

JUC 之ThreadPoolExecutor實現原理分析 2

結合上圖補充幾點:

線程池中創建的用於執行提交任務的線程的引用被Worker對象持有。 Worker會去執行提交的任務,如果提交的任務已執行完Worker會循環地從workQueue(即圖中的BlockingQueue)中poll或take任務執行。主線程調用ThreadPoolExecutor的prestartCoreThread()或prestartAllCoreThreads()方法可以在任務還沒有提交到線程池前,先創建用於執行提交任務的Worker,這些Worker將等待任務提交。線程池飽和時默認地拒絕策略為AbortPolicy策略,拋出RejectedExecutionException異常,上圖中CallerRunsPolicy表達的不是默認地拒絕策略,而是CallerRunsPolicy策略是會將提交的任務(Task)交給主線程執行。即主線程調用Task.run()方法。

ThreadPoolExecutor源碼分析

JUC 之ThreadPoolExecutor實現原理分析 3

ThreadPoolExecutor的UML類圖如上圖,其中Executor提供最基礎的任務執行的抽象void execute(Runnable command)方法,而ExecutorService在其基礎上擴展的管理線程池的一些方法shutdown()、shutdownNow()、isShutdown( ) 與isTerminated()等,同時增加了用三個重載的submit方法,用於獲取任務的執行結果。 submit可以提交Callable類型的任務,也可提交Runnable類型的任務。 AbstractExecutorService類提供了newTaskFor將提交的Callable與Runnable類型任務轉為FutureTask,同時提供了sumbit與invoke的默認實現,具體的任務執行邏輯交由子類ThreadPoolExecutor的execute方法。不管是調用submit還是execute的提交的任務,最終都交由ThreadPoolExecutor的execute方法執行。

execute方法是分析ThreadPoolExecutor源碼的入口。

分析execute方法前先看一下ThreadPoolExecutor裡面的核心變量與類。

//线程池状态与线程池中有效线程数控制变量,AtomicInteger变量的高3位用于
//保存线程池状态,低29位用于保存线程池中有效线程数。
//程线程对应状态如下:
// 1、RUNNING: 运行中,接收新的任务或处理队列中的任务 值为-536870912
// 2、SHUTDOWN: 关闭,不再接收新的任务,但会处理队列中的任务 值为0
// 3、STOP: 停止,不再接收新的任务,也不处理队列中的任务,并中断正在处理的任务 值为536870912
// 4、TIDYING: 所有任务已结束,队列大小为0,转变为TIDYING状态的线程将会执行terminated() hook 方法 值为1073741824
// 5、TERMINATED: 结束,terminated() 已被执行完 值为1610612736
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

與ctl變量有關的操作方法

  //获取线程池的运行状态runState
  //CAPACITY 二进制值为: 00011111111111111111111111111111
//~CAPACITY 按位取反为:11100000000000000000000000000000
//ctl&~CAPACITY 低29全为0,得到高3位即线程池的runState
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程池中有效的线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
  //根据runState与workerCount计算出ctl的值
private static int ctlOf(int rs, int wc) { return rs | wc; }
//判断线程池是否处于运行中
private static boolean isRunning(int c) {return c < SHUTDOWN;}

其它核心成員變量

//工作队列,提交任务超过corePoolSize时,任务被保证在workQueue中
private final BlockingQueue workQueue;
//处理wokers的锁
private final ReentrantLock mainLock = new ReentrantLock();
//工作作线程集合
private final Hash workers = new HashSet();
//用于支持awaitTermination方法的条件
private final Condition termination = mainLock.newCondition();
//曾经创建过的最大工作线程数
private int largestPoolSize;
//线程池中已完成的总任务数
private long completedTaskCount;
//线程池创建执行提交任务对应线程时采用的线程工厂
private volatile ThreadFactory threadFactory;
//线程池饱和时,拒绝策略
private volatile RejectedExecutionHandler handler;
//allowCoreThreadTimeOut为true时,无任务时情况下核心线程允许存活时间;
//线程池中超过核心线程数,那部分工作线程,无任务时情况下核心线程允许存活时间。
private volatile long keepAliveTime;
//核心工作线程是以超时的方式还是阻塞的方式尝试从workQueue队列里面获取任务,
//当以超时的方式获取时,如果在指定时间内还没有获取到任务工作线程run方法将执
//行完毕,对应工作线程被GC回收
private volatile boolean allowCoreThreadTimeOut;
//线程池中核心工作线程数
private volatile int corePoolSize;
//线程池中最大工作线程数
private volatile int maximumPoolSize;
// 线程池饱和时,默认拒绝策略 直接抛出异常
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

工作線程Worker類,Worker類利用AQS框架實現了一個簡單的非重入的互斥鎖, 實現互斥鎖主要目的是為了中斷的時候判斷線程是在空閒還是運行,可以看後面shutdown和shutdownNow方法的分析。涉及AQS部分暫時不深入分析,後面再寫專關於AQS的文章。

private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
  //用于执行提交任务的线程
final Thread thread;
//第一个要执行的任务,可能为null
Runnable firstTask;
//每个工作线程执行的任务数量
volatile long completedTasks;
Worker(Runnable firstTask) {
       //阻止中断,直到运行runWorker方法
setState(-1);
this.firstTask = firstTask;
       //利用线程工厂创建工作线程,同时让当前Worker.run方法去执行提交的任务
this.thread = getThreadFactory().newThread(this);
}
     //工作线程执行任务的入口,具体执行任务代理给runWorker方法
public void run() {
runWorker(this);
}
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
//设置为排它锁
setExclusiveOwnerThread(Thread.currentThread());
//成功获取锁
return true;
}
//到同步队列中自旋
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
//省略部分源码
}

ThreadPoolExecutor之execute方法

execute方法執行流程可以概括為

如果工作線程數量小於核心線程數量,則創建新的線程處理提交的任務。

如果工作線程數量大於等於核心線程數量,且沒有超過最大線程數,則將新提交的任務,加入工作隊列中等待執行。

如果工作線程數量大於等於核心線程數量,且工作隊列已滿,工作線程數量又於小最大線程數量,則創建新的線程處理提交的任務。

如果工作線程數量大於最大線程數量或者線程池是不在運行,執行拒絕策略。

上面流程圖其實已說明過了execute的大體執行過程。其中,addWorker方法內部會檢測線程池的運行狀態,判斷任務是否應該被成功提交。

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
     // 工作线程数量小于核心线程数,调用addWorker方法创建工作线程。
// 提交任务command作为Worder的第一个任务执行。
if (workerCountOf(c) maximumPoolSize)
else if (!addWorker(command, false))
reject(command);
}

ThreadPoolExecutor之addWorker方法

addWorker創建了ThreadPoolExecutor中用於執行提交任務的線程,這個過程同時把任務與執行任務的線程封裝到Worker對像中。同時addWorker還啟動了用於執行任務的線程,而具體任務的執行,則代理給了ThreadPoolExecutor的runWorkers方法。

private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
       //线程池状态为非RUNNING &&
        //(线程池状态为非SHUTDOWN || firstTask不为null || 队列为空) 三者中的一者
        //组合一下分别是
        //1、线程池状态为非RUNNING && 线程池状态为非SHUTDOWN,
        //即,线程池状态为 (STOP || TIDYING || TERMINATED)
        //此时线程池不在接受新的任务,通过addWorker新提交的任务会失败       
        //2、线程池状态为非RUNNING && firstTask不为null
//即,线程池状态为 (SHUTDOWN || STOP || TIDYING || TERMINATED) && firstTask不为null
//此时线程池不在接受新的任务,但有处理队列里的任务,通过addWorker新提交的任务会失败
//3、线程池状态为非RUNNING && 队列为空
//即,线程池状态为(SHUTDOWN || STOP || TIDYING || TERMINATED)&& 队列为空
//此时线程池不在接受新的任务,因为队列中没有任务要处理,
        //所以没必要调用addWorker(null, false),创建新的线程去处理工作队列的任务
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
       // 线程池状态为RUNNING 或者 (线程池状态为SHUTDOWN状态,且队列中还有任务需要执行)
for (;;) {
int wc = workerCountOf(c);
          //工作线程数过大最大值,或者超过核心线程数或超过最大线程数,都返回false
          if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
          //原子方式设置线程池中线程数成功,则跳出重试的循环
          if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
          // 如果线程池的状态发生变化则重试
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
       //创建worker对象,worker对象内部
//利用线程工厂创建一个线程去执行提交的任务
       //这个线程的target Runnable为 worker本身,
       //最终调用worker.run执行提交的任务
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
            // 重新检测线程池的状态,在获取得锁前一步,线程池可能已被终止
            // 线程池状态为RUNNING 或者 (线程池状态为SHUTDOWN状态,且队列中还有任务需要执行)
if (rs largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//worker添加成功
if (workerAdded) {
            //启动worker内部线程,worker内部线程的
            //target Runnable为worker本身,
//将运行worker的run方法,run内部调用ThreadPoolExecutor.runWorkers方法
t.start();
workerStarted = true;
}
}
} finally {
       //获取得锁前一步,线程池已被终止导致
//workerAdded失败或线程没start。
if (! workerStarted)
         //会调用tryTerminate方法
addWorkerFailed(w);
}
return workerStarted;
}

ThreadPoolExecutor之runWorkers方法

runWorkers方法首先會執行woker對像中的firstTask,當firstTask執行完後,會通過getTask方法循環地從workerQueue(工作隊列)中獲取任務去執行。當workerQueue中沒有任務,getTask方法會阻塞掛起。 runWorkers中在任務執行前調用了beforeExecute擴展點,在任務執行後調用了afterExecute擴展點。最後則調用processWorkerExit方法作一下清理工作。

final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//Worker的构造函数中通过setState(-1)抑制了线程中断,
//这里通过unlock允许中断
w.unlock();
boolean completedAbruptly = true;
try {
       //首先执行worker中的firestTask,
       //然后循环地从workQueue中拉取任务执行
while (task != null || (task = getTask()) != null) {
w.lock();
//如果线程池处于停止中,
          //即线程池处于STOP、TIDYING、TERMINATED状态,
          // 要确保线程被中断。如果没有确保不被中断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
            //任务执行前扩展点
beforeExecute(wt, task);
Throwable thrown = null;
try {
              //执行任务
task.run();
} 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);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
       //将worker中workerSet中清除,统计完成任务数
       //同时调用tryTerminate方法尝试终止线程池
processWorkerExit(w, completedAbruptly);
}
}

ThreadPoolExecutor之getTask方法

getTask方法主要用於從workQueue中取出任務交給runWorker方法去執行提交的任務,同時完了線程池中核心線程是否要allowCoreThreadTimeOut與線程池中線程數量超過maximunPoolSize時timeOut處理。核心工作線程是以超時的方式還是阻塞的方式嘗試從workQueue隊列裡面獲取任務,當以超時的方式獲取時,如果在指定時間內還沒有獲取到任務工作線程run方法將執行完畢,對應工作線程被GC回收。

分析execute方法前先看一下ThreadPoolExecutor裡面的核心變量與類。

private Runnable getTask() {
     //上一次从workQueue.poll方法是否超时
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 1、线程池为SHUTDOWN以上状态时且工作队列为空时,
// 此时没有任务,直接返回null
// 2、线程池为STOP以上状态时,
       // 此时不用处理工作队列中的任务直接返回
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//工作线程是否要在指的timeout时间内被清理
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
       //1、wc > maximumPoolSize && (wc > 1 || workQueue.isEmpty())
//这个种情况按理不会出现??
 //2、(timed && timedOut) && (wc > 1 || workQueue.isEmpty())
        //影响超时处理
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
       }
try {
//影响timeOut的方式从workQueue中获取任务,
          //或者以阻塞的方式从workQueue中获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}

ThreadPoolExecutor之processWorkerExit方法

processWorkerExit方法統計了線程池中總執行的任務數,同時嘗試終止線程池。另外還加上當線程池的runState為RUNNING或SHUTDOWN時,由於核心線程數允許超時導致線程池中沒有線程處理工作隊列中任務的邏輯。即通過addWorker(null,false)創建一個新的線程來處理工作隊列中的任務。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
     //runWokder执行异常时,让ctl中有效线程数量减一,
     //runWokder正常执行时,getTask方法中workerCount会被减一
if (completedAbruptly)
       //ctl中有效线程数量减一
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//统计已完成的任务数
completedTaskCount += w.completedTasks;
       //从wordkerSet中去worker
workers.remove(w);
} finally {
mainLock.unlock();
}
     //尝试终止线程池
tryTerminate();
int c = ctl.get();
     //线程池的runState为RUNNING或SHUTDOWN时
if (runStateLessThan(c, STOP)) {
//runWorker正常执行
if (!completedAbruptly) {
          //线程池最小空闲数,允许core thread超时就是0,
          //否则就是corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//任务队列不为空,则至少要一个线程处理任务队列中的任务
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//线程池中有线程处理任务中的任务直接返回
if (workerCountOf(c) >= min)
return; // replacement not needed
}
       //线程池中没有线程处理任务队列中的任务,
       //创建一个线程处理任务队列中的任务
addWorker(null, false);
}
}

ThreadPoolExecutor之tryTerminate方法

tryTerminate方法會嘗試終止線程池,如果線程池還不能終止則直接返回。如果確定可以終止的話,會調用terminated擴展點方法,執行線程池終止前想要做的工作。

final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 1、线程池还处于RUNNING状态直接返回
// 2、线程池状态大于TIDYING,线程池已经停止了或在停止
// 3、线程池为SHUTDOWN状态但是任务队列非空直接返回
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
       //线程池中还有工作线程,中断工作线程,退出
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
          //cas方式设置ctl状态,成功执行terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//线程池终止前执行的扩展点方法
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
//通知awaitTermination方法,继续执行
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}

ThreadPoolExecutor之shutdown方法

shutdown方法會先將線程池的狀態設置為SHUTDOWN,然後向線程池中的所有線程發出中斷信號。最後會調用tryTermiate方法嘗試終止線程。處理SHUTDOWN狀態的線程池,不接受新的任務,但會執行工作隊列中的任務。

public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
       //确保调用者用权限关闭线程池
checkShutdownAccess();
       //自旋的方法设置线程池的状态为SHUTDOWN
advanceRunState(SHUTDOWN);
       // 注意这里是中断所有空闲的线程:runWorker中等待的线程被中断 → 进入processWorkerExit →
// tryTerminate方法中会保证队列中剩余的任务得到执行。
interruptIdleWorkers();
       // hook for ScheduledThreadPoolExecutor
onShutdown();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}

ThreadPoolExecutor之shutdownNow方法

shutdownNow方法會先將線程池的狀態設置為SHUTDOWN,然後向線程池中的所有線程發出中斷信號。最後會調用tryTermiate方法嘗試終止線程。處於STOP狀態的線程池,不接受新的任務,同時由於調用了drainQueue使得workQueue中任務全被刪除,workQueue中的任務不被執行。

public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
       //确保调用者用权限关闭线程池
checkShutdownAccess();
       //自旋的方法设置线程池的状态为SHUTDOWN
advanceRunState(STOP);
       //中断所有线程池中所有线程
interruptWorkers();
//获取未执行的任务
       tasks = drainQueue();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
return tasks;
}

shutDown方法與shutDownNow方法,最主要的區別在於shutDown調用的是

interruptIdleWorkers()方法,而shutDownNow調用的是interruptWorkers()方法。

ThreadPoolExecutor之interruptIdleWorkers

interruptIdleWorkers方法只會中斷空閒的線程。這點是通過w.tryLock實現的,由於runWorker方法中在worker在執行任務前會先調用worker的lock方法。

所以tryLock方法成功時,當前的worker一定處於空閒狀態。

private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//判断worker持有的线程是否已被中断
       //且能通过tryLock能获取到锁。
       //由于runWorker方法中执行任务时会先lock,
       //如果能tryLock说线程不在执行任务,
       //保证了中断的肯定是空闲的线程。
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
}
finally {
mainLock.unlock();
}
}
ThreadPoolExecutor之interruptWorkers方法
 private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
         //调用worker的interruptIfStarted方法中断
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
Worker之interruptIfStarted方法
void interruptIfStarted() {
Thread t;
//state为0表示worker unLock,1表示worker lock,
//不管worker是在runWorker还是idle,全部进行中断
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}

ThreadPoolExecutor之prestartCoreThread方法

prestartCoreThread方法首先判斷當前線程池中的線程數是否小於核心線程數,如果小於則調用addWorker創建一個工作線程。該工作線程等待處理後面將要提交的任務

public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize && addWorker(null, true); } ThreadPoolExecutor之prestartAllCoreThreads方法 prestartAllCoreThreads与prestartCoreThread方法类似 public int prestartAllCoreThreads() { int n = 0; while (addWorker(null, true)) ++n; return n; }

這也是之前ThreadPoolExecutor更加直觀的整體運行圖中prestartAllCoreThreads與prestartCoreThread指向線程池中核心線程執行有者Worker的那部分。

內容有點點多,如果之前沒有看過ThreadPoolExecutor的源碼的話一下子看下來可能比較累,大家可以先收藏後面再根據這個分析的路線一個個看具體方法的分析。本想在這篇文章最後分享一下線程池的一些面試考點與使用時的注意點,但內容實在太多,只能放到下一篇中。

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

點贊,轉發,有你們的『點贊和評論』,才是我創造的動力。關注公眾號『 java爛豬皮 』,不定期分享原創知識。同時可以期待後續文章ing🚀

JUC 之ThreadPoolExecutor實現原理分析 4

作者:葉易出處:https://club.perfma.com/article/1719588