不怕難之線程池原理

一、線程池狀態(tài)

ThreadPoolExecutor 是 JDK 中的線程池實(shí)現(xiàn),這個(gè)類實(shí)現(xiàn)了一個(gè)線程池需要的各個(gè)方法,它實(shí)現(xiàn)了任務(wù)提交、線程管理、監(jiān)控等等方法。

原理圖:


private final AtomicIntegerctl =new AtomicInteger(ctlOf(RUNNING, 0));

? ? // 這里 COUNT_BITS 設(shè)置為 29(32-3),意味著前三位用于存放線程狀態(tài),后29位用于存放線程數(shù)

// 很多初學(xué)者很喜歡在自己的代碼中寫很多 29 這種數(shù)字,或者某個(gè)特殊的字符串,然后分布在各個(gè)地方,這是非常糟糕的

? ? private static final int COUNT_BITS = Integer.SIZE -3;

? ? // 000 11111111111111111111111111111

// 這里得到的是 29 個(gè) 1,也就是說線程池的最大線程數(shù)是 2^29-1=536870911

// 以我們現(xiàn)在計(jì)算機(jī)的實(shí)際情況,這個(gè)數(shù)量還是夠用的

? ? private static final int CAPACITY? = (1 <

? ? // 我們說了,線程池的狀態(tài)存放在高 3 位中

// 運(yùn)算結(jié)果為 111跟29個(gè)0:111 00000000000000000000000000000

? ? private static final int RUNNING? ? = -1 <

? ? // 000 00000000000000000000000000000

? ? private static final int SHUTDOWN? =0 <

? ? // 001 00000000000000000000000000000

? ? private static final int STOP? ? ? =1 <

? ? // 010 00000000000000000000000000000

? ? private static final int TIDYING? ? =2 <

? ? // 011 00000000000000000000000000000

? ? private static final int TERMINATED =3 <

? ? // 將整數(shù) c 的低 29 位修改為 0,就得到了線程池的狀態(tài)

? ? private static int runStateOf(int c)? ? {return c & ~CAPACITY; }

// 將整數(shù) c 的高 3 為修改為 0,就得到了線程池中的線程數(shù)

? ? private static int workerCountOf(int c)? {return c &CAPACITY; }

private static int ctlOf(int rs, int wc) {return rs | wc; }

/*

* Bit field accessors that don't require unpacking ctl.

* These depend on the bit layout and on workerCount being never negative.

*/

? ? private static boolean runStateLessThan(int c, int s) {

return c < s;

? ? }

private static boolean runStateAtLeast(int c, int s) {

return c >= s;

? ? }

private static boolean isRunning(int c) {

return c

? ? }


RUNNING:這個(gè)沒什么好說的,這是最正常的狀態(tài):接受新的任務(wù),處理等待隊(duì)列中的任務(wù)

SHUTDOWN:不接受新的任務(wù)提交,但是會(huì)繼續(xù)處理等待隊(duì)列中的任務(wù)

STOP:不接受新的任務(wù)提交,不再處理等待隊(duì)列中的任務(wù),中斷正在執(zhí)行任務(wù)的線程

TIDYING:所有的任務(wù)都銷毀了,workCount 為 0。線程池的狀態(tài)在轉(zhuǎn)換為 TIDYING 狀態(tài)時(shí),會(huì)執(zhí)行鉤子方法 terminated()

TERMINATED:terminated() 方法結(jié)束后,線程池的狀態(tài)就會(huì)變成這個(gè)


二、Worker對(duì)象設(shè)計(jì)

private final class Worker

extends AbstractQueuedSynchronizer

implements Runnable

{

private static final long serialVersionUID =6138294804551838833L;

? ? // 這個(gè)是真正的線程,任務(wù)靠你啦

? ? final Threadthread;

? ? // 前面說了,這里的 Runnable 是任務(wù)。為什么叫 firstTask?因?yàn)樵趧?chuàng)建線程的時(shí)候,如果同時(shí)指定了

// 這個(gè)線程起來以后需要執(zhí)行的第一個(gè)任務(wù),那么第一個(gè)任務(wù)就是存放在這里的(線程可不止執(zhí)行這一個(gè)任務(wù))

// 當(dāng)然了,也可以為 null,這樣線程起來了,自己到任務(wù)隊(duì)列(BlockingQueue)中取任務(wù)(getTask 方法)就行了

? ? RunnablefirstTask;

? ? // 用于存放此線程完全的任務(wù)數(shù),注意了,這里用了 volatile,保證可見性

? ? volatile long completedTasks;

? ? // Worker 只有這一個(gè)構(gòu)造方法,傳入 firstTask,也可以傳 null

? ? Worker(Runnable firstTask) {

setState(-1); // inhibit interrupts until runWorker

? ? ? ? this.firstTask = firstTask;

? ? ? ? // 調(diào)用 ThreadFactory 來創(chuàng)建一個(gè)新的線程

? ? ? ? this.thread = getThreadFactory().newThread(this);

? ? }

// 這里調(diào)用了外部類的 runWorker 方法

? ? public void run() {

runWorker(this);

? ? }

...// 其他幾個(gè)方法沒什么好看的,就是用 AQS 操作,來獲取這個(gè)線程的執(zhí)行權(quán),用了獨(dú)占鎖

}


三、execute

public void execute(Runnable command) {

if (command ==null)

throw new NullPointerException();

? ? // 前面說的那個(gè)表示 “線程池狀態(tài)” 和 “線程數(shù)” 的整數(shù)

? ? int c = ctl.get();

? ? // 如果當(dāng)前線程數(shù)少于核心線程數(shù),那么直接添加一個(gè) worker 來執(zhí)行任務(wù),

// 創(chuàng)建一個(gè)新的線程,并把當(dāng)前任務(wù) command 作為這個(gè)線程的第一個(gè)任務(wù)(firstTask)

? ? if (workerCountOf(c) < corePoolSize) {

// 添加任務(wù)成功,那么就結(jié)束了。提交任務(wù)嘛,線程池已經(jīng)接受了這個(gè)任務(wù),這個(gè)方法也就可以返回了

// 至于執(zhí)行的結(jié)果,到時(shí)候會(huì)包裝到 FutureTask 中。

// 返回 false 代表線程池不允許提交任務(wù)

? ? ? ? if (addWorker(command, true))

return;

? ? ? ? c = ctl.get();

? ? }

// 到這里說明,要么當(dāng)前線程數(shù)大于等于核心線程數(shù),要么剛剛 addWorker 失敗了

// 如果線程池處于 RUNNING 狀態(tài),把這個(gè)任務(wù)添加到任務(wù)隊(duì)列 workQueue 中

? ? if (isRunning(c) && workQueue.offer(command)) {

/* 這里面說的是,如果任務(wù)進(jìn)入了 workQueue,我們是否需要開啟新的線程

* 因?yàn)榫€程數(shù)在 [0, corePoolSize) 是無條件開啟新的線程

* 如果線程數(shù)已經(jīng)大于等于 corePoolSize,那么將任務(wù)添加到隊(duì)列中,然后進(jìn)到這里

*/

? ? ? ? int recheck = ctl.get();

? ? ? ? // 如果線程池已不處于 RUNNING 狀態(tài),那么移除已經(jīng)入隊(duì)的這個(gè)任務(wù),并且執(zhí)行拒絕策略

? ? ? ? if (! isRunning(recheck) && remove(command))

reject(command);

? ? ? ? ? ? // 如果線程池還是 RUNNING 的,并且線程數(shù)為 0,那么開啟新的線程

// 到這里,我們知道了,這塊代碼的真正意圖是:擔(dān)心任務(wù)提交到隊(duì)列中了,但是線程都關(guān)閉了

? ? ? ? else if (workerCountOf(recheck) ==0)

addWorker(null, false);

? ? }

// 如果 workQueue 隊(duì)列滿了,那么進(jìn)入到這個(gè)分支

// 以 maximumPoolSize 為界創(chuàng)建新的 worker,

// 如果失敗,說明當(dāng)前線程數(shù)已經(jīng)達(dá)到 maximumPoolSize,執(zhí)行拒絕策略

? ? else if (!addWorker(command, false))

reject(command);

}

四、添加任務(wù)

// 第一個(gè)參數(shù)是準(zhǔn)備提交給這個(gè)線程執(zhí)行的任務(wù),之前說了,可以為 null

// 第二個(gè)參數(shù)為 true 代表使用核心線程數(shù) corePoolSize 作為創(chuàng)建線程的界線,也就說創(chuàng)建這個(gè)線程的時(shí)候,

//? ? ? ? 如果線程池中的線程總數(shù)已經(jīng)達(dá)到 corePoolSize,那么不能響應(yīng)這次創(chuàng)建線程的請(qǐng)求

//? ? ? ? 如果是 false,代表使用最大線程數(shù) maximumPoolSize 作為界線

private boolean addWorker(Runnable firstTask, boolean core) {

retry:

for (;;) {

int c = ctl.get();

? ? ? ? int rs = runStateOf(c);

? ? ? ? // 這個(gè)非常不好理解

// 如果線程池已關(guān)閉,并滿足以下條件之一,那么不創(chuàng)建新的 worker:

// 1. 線程池狀態(tài)大于 SHUTDOWN,其實(shí)也就是 STOP, TIDYING, 或 TERMINATED

// 2. firstTask != null

// 3. workQueue.isEmpty()

// 簡單分析下:

// 還是狀態(tài)控制的問題,當(dāng)線程池處于 SHUTDOWN 的時(shí)候,不允許提交任務(wù),但是已有的任務(wù)繼續(xù)執(zhí)行

// 當(dāng)狀態(tài)大于 SHUTDOWN 時(shí),不允許提交任務(wù),且中斷正在執(zhí)行的任務(wù)

// 多說一句:如果線程池處于 SHUTDOWN,但是 firstTask 為 null,且 workQueue 非空,那么是允許創(chuàng)建 worker 的

? ? ? ? if (rs >= SHUTDOWN &&

! (rs == SHUTDOWN &&

firstTask ==null &&

! workQueue.isEmpty()))

return false;

? ? ? ? for (;;) {

int wc = workerCountOf(c);

? ? ? ? ? ? if (wc >= CAPACITY ||

wc >= (core ? corePoolSize : maximumPoolSize))

return false;

? ? ? ? ? ? // 如果成功,那么就是所有創(chuàng)建線程前的條件校驗(yàn)都滿足了,準(zhǔn)備創(chuàng)建線程執(zhí)行任務(wù)了

// 這里失敗的話,說明有其他線程也在嘗試往線程池中創(chuàng)建線程

? ? ? ? ? ? if (compareAndIncrementWorkerCount(c))

break retry;

? ? ? ? ? ? // 由于有并發(fā),重新再讀取一下 ctl

? ? ? ? ? ? c = ctl.get();

? ? ? ? ? ? // 正常如果是 CAS 失敗的話,進(jìn)到下一個(gè)里層的for循環(huán)就可以了

// 可是如果是因?yàn)槠渌€程的操作,導(dǎo)致線程池的狀態(tài)發(fā)生了變更,如有其他線程關(guān)閉了這個(gè)線程池

// 那么需要回到外層的for循環(huán)

? ? ? ? ? ? if (runStateOf(c) != rs)

continue retry;

? ? ? ? ? ? // else CAS failed due to workerCount change; retry inner loop

? ? ? ? }

}

/*

* 到這里,我們認(rèn)為在當(dāng)前這個(gè)時(shí)刻,可以開始創(chuàng)建線程來執(zhí)行任務(wù)了,

* 因?yàn)樵撔r?yàn)的都校驗(yàn)了,至于以后會(huì)發(fā)生什么,那是以后的事,至少當(dāng)前是滿足條件的

*/

// worker 是否已經(jīng)啟動(dòng)

? ? boolean workerStarted =false;

? ? // 是否已將這個(gè) worker 添加到 workers 這個(gè) HashSet 中

? ? boolean workerAdded =false;

? ? Worker w =null;

? ? try {

final ReentrantLock mainLock =this.mainLock;

? ? ? ? // 把 firstTask 傳給 worker 的構(gòu)造方法

? ? ? ? w =new Worker(firstTask);

? ? ? ? // 取 worker 中的線程對(duì)象,之前說了,Worker的構(gòu)造方法會(huì)調(diào)用 ThreadFactory 來創(chuàng)建一個(gè)新的線程

? ? ? ? final Thread t = w.thread;

? ? ? ? if (t !=null) {

// 這個(gè)是整個(gè)類的全局鎖,持有這個(gè)鎖才能讓下面的操作“順理成章”,

// 因?yàn)殛P(guān)閉一個(gè)線程池需要這個(gè)鎖,至少我持有鎖的期間,線程池不會(huì)被關(guān)閉

? ? ? ? ? ? mainLock.lock();

? ? ? ? ? ? try {

int c = ctl.get();

? ? ? ? ? ? ? ? int rs = runStateOf(c);

? ? ? ? ? ? ? ? // 小于 SHUTTDOWN 那就是 RUNNING,這個(gè)自不必說,是最正常的情況

// 如果等于 SHUTDOWN,前面說了,不接受新的任務(wù),但是會(huì)繼續(xù)執(zhí)行等待隊(duì)列中的任務(wù)

? ? ? ? ? ? ? ? if (rs < SHUTDOWN ||

(rs == SHUTDOWN && firstTask ==null)) {

// worker 里面的 thread 可不能是已經(jīng)啟動(dòng)的

? ? ? ? ? ? ? ? ? ? if (t.isAlive())

throw new IllegalThreadStateException();

? ? ? ? ? ? ? ? ? ? // 加到 workers 這個(gè) HashSet 中

? ? ? ? ? ? ? ? ? ? workers.add(w);

? ? ? ? ? ? ? ? ? ? int s = workers.size();

? ? ? ? ? ? ? ? ? ? // largestPoolSize 用于記錄 workers 中的個(gè)數(shù)的最大值

// 因?yàn)?workers 是不斷增加減少的,通過這個(gè)值可以知道線程池的大小曾經(jīng)達(dá)到的最大值

? ? ? ? ? ? ? ? ? ? if (s > largestPoolSize)

largestPoolSize = s;

? ? ? ? ? ? ? ? ? ? workerAdded =true;

? ? ? ? ? ? ? ? }

}finally {

mainLock.unlock();

? ? ? ? ? ? }

// 添加成功的話,啟動(dòng)這個(gè)線程

? ? ? ? ? ? if (workerAdded) {

// 啟動(dòng)線程

? ? ? ? ? ? ? ? t.start();

? ? ? ? ? ? ? ? workerStarted =true;

? ? ? ? ? ? }

}

}finally {

// 如果線程沒有啟動(dòng),需要做一些清理工作,如前面 workCount 加了 1,將其減掉

? ? ? ? if (! workerStarted)

addWorkerFailed(w);

? ? }

// 返回線程是否啟動(dòng)成功

? ? return workerStarted;

}

五、執(zhí)行任務(wù)

// 此方法由 worker 線程啟動(dòng)后調(diào)用,這里用一個(gè) while 循環(huán)來不斷地從等待隊(duì)列中獲取任務(wù)并執(zhí)行

// 前面說了,worker 在初始化的時(shí)候,可以指定 firstTask,那么第一個(gè)任務(wù)也就可以不需要從隊(duì)列中獲取

final void runWorker(Worker w) {

//

? ? Thread wt = Thread.currentThread();

? ? // 該線程的第一個(gè)任務(wù)(如果有的話)

? ? Runnable task = w.firstTask;

? ? w.firstTask =null;

? ? w.unlock(); // allow interrupts

? ? boolean completedAbruptly =true;

? ? try {

// 循環(huán)調(diào)用 getTask 獲取任務(wù)

? ? ? ? while (task !=null || (task = getTask()) !=null) {

w.lock();

? ? ? ? ? ? // 如果線程池狀態(tài)大于等于 STOP,那么意味著該線程也要中斷

? ? ? ? ? ? if ((runStateAtLeast(ctl.get(), STOP) ||

(Thread.interrupted() &&

runStateAtLeast(ctl.get(), STOP))) &&

!wt.isInterrupted())

wt.interrupt();

? ? ? ? ? ? try {

// 這是一個(gè)鉤子方法,留給需要的子類實(shí)現(xiàn)

? ? ? ? ? ? ? ? beforeExecute(wt, task);

? ? ? ? ? ? ? ? Throwable thrown =null;

? ? ? ? ? ? ? ? try {

// 到這里終于可以執(zhí)行任務(wù)了

? ? ? ? ? ? ? ? ? ? task.run();

? ? ? ? ? ? ? ? }catch (RuntimeException x) {

thrown = x; throw x;

? ? ? ? ? ? ? ? }catch (Error x) {

thrown = x; throw x;

? ? ? ? ? ? ? ? }catch (Throwable x) {

// 這里不允許拋出 Throwable,所以轉(zhuǎn)換為 Error

? ? ? ? ? ? ? ? ? ? thrown = x; throw new Error(x);

? ? ? ? ? ? ? ? }finally {

// 也是一個(gè)鉤子方法,將 task 和異常作為參數(shù),留給需要的子類實(shí)現(xiàn)

? ? ? ? ? ? ? ? ? ? afterExecute(task, thrown);

? ? ? ? ? ? ? ? }

}finally {

// 置空 task,準(zhǔn)備 getTask 獲取下一個(gè)任務(wù)

? ? ? ? ? ? ? ? task =null;

? ? ? ? ? ? ? ? // 累加完成的任務(wù)數(shù)

? ? ? ? ? ? ? ? w.completedTasks++;

? ? ? ? ? ? ? ? // 釋放掉 worker 的獨(dú)占鎖

? ? ? ? ? ? ? ? w.unlock();

? ? ? ? ? ? }

}

completedAbruptly =false;

? ? }finally {

// 如果到這里,需要執(zhí)行線程關(guān)閉:

// 1. 說明 getTask 返回 null,也就是說,這個(gè) worker 的使命結(jié)束了,執(zhí)行關(guān)閉

// 2. 任務(wù)執(zhí)行過程中發(fā)生了異常

// 第一種情況,已經(jīng)在代碼處理了將 workCount 減 1,這個(gè)在 getTask 方法分析中會(huì)說

// 第二種情況,workCount 沒有進(jìn)行處理,所以需要在 processWorkerExit 中處理

// 限于篇幅,我不準(zhǔn)備分析這個(gè)方法了,感興趣的讀者請(qǐng)自行分析源碼

? ? ? ? processWorkerExit(w, completedAbruptly);

? ? }

}

六、獲取任務(wù)

// 此方法有三種可能:

// 1. 阻塞直到獲取到任務(wù)返回。我們知道,默認(rèn) corePoolSize 之內(nèi)的線程是不會(huì)被回收的,

//? ? ? 它們會(huì)一直等待任務(wù)

// 2. 超時(shí)退出。keepAliveTime 起作用的時(shí)候,也就是如果這么多時(shí)間內(nèi)都沒有任務(wù),那么應(yīng)該執(zhí)行關(guān)閉

// 3. 如果發(fā)生了以下條件,此方法必須返回 null:

//? ? - 池中有大于 maximumPoolSize 個(gè) workers 存在(通過調(diào)用 setMaximumPoolSize 進(jìn)行設(shè)置)

//? ? - 線程池處于 SHUTDOWN,而且 workQueue 是空的,前面說了,這種不再接受新的任務(wù)

//? ? - 線程池處于 STOP,不僅不接受新的線程,連 workQueue 中的線程也不再執(zhí)行

private RunnablegetTask() {

boolean timedOut =false; // Did the last poll() time out?

? ? retry:

for (;;) {

int c = ctl.get();

? ? ? ? int rs = runStateOf(c);

? ? ? ? // 兩種可能

// 1. rs == SHUTDOWN && workQueue.isEmpty()

// 2. rs >= STOP

? ? ? ? if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {

// CAS 操作,減少工作線程數(shù)

? ? ? ? ? ? decrementWorkerCount();

return null;

? ? ? ? }

boolean timed;? ? ? // Are workers subject to culling?

? ? ? ? for (;;) {

int wc = workerCountOf(c);

? ? ? ? ? ? // 允許核心線程數(shù)內(nèi)的線程回收,或當(dāng)前線程數(shù)超過了核心線程數(shù),那么有可能發(fā)生超時(shí)關(guān)閉

? ? ? ? ? ? timed = allowCoreThreadTimeOut || wc > corePoolSize;

? ? ? ? ? ? // 這里 break,是為了不往下執(zhí)行后一個(gè) if (compareAndDecrementWorkerCount(c))

// 兩個(gè) if 一起看:如果當(dāng)前線程數(shù) wc > maximumPoolSize,或者超時(shí),都返回 null

// 那這里的問題來了,wc > maximumPoolSize 的情況,為什么要返回 null?

//? ? 換句話說,返回 null 意味著關(guān)閉線程。

// 那是因?yàn)橛锌赡荛_發(fā)者調(diào)用了 setMaximumPoolSize 將線程池的 maximumPoolSize 調(diào)小了

? ? ? ? ? ? if (wc <= maximumPoolSize && ! (timedOut && timed))

break;

? ? ? ? ? ? if (compareAndDecrementWorkerCount(c))

return null;

? ? ? ? ? ? c = ctl.get();? // Re-read ctl

// compareAndDecrementWorkerCount(c) 失敗,線程池中的線程數(shù)發(fā)生了改變

? ? ? ? ? ? if (runStateOf(c) != rs)

continue retry;

? ? ? ? ? ? // else CAS failed due to workerCount change; retry inner loop

? ? ? ? }

// wc <= maximumPoolSize 同時(shí)沒有超時(shí)

? ? ? ? try {

// 到 workQueue 中獲取任務(wù)

? ? ? ? ? ? Runnable r = timed ?

workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :

workQueue.take();

? ? ? ? ? ? if (r !=null)

return r;

? ? ? ? ? ? timedOut =true;

? ? ? ? }catch (InterruptedException retry) {

// 如果此 worker 發(fā)生了中斷,采取的方案是重試

// 解釋下為什么會(huì)發(fā)生中斷,這個(gè)讀者要去看 setMaximumPoolSize 方法,

// 如果開發(fā)者將 maximumPoolSize 調(diào)小了,導(dǎo)致其小于當(dāng)前的 workers 數(shù)量,

// 那么意味著超出的部分線程要被關(guān)閉。重新進(jìn)入 for 循環(huán),自然會(huì)有部分線程會(huì)返回 null

? ? ? ? ? ? timedOut =false;

? ? ? ? }

}

}

七、案例分析

ThreadPoolExecutor pool =new ThreadPoolExecutor(1,2,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));

MyTask task1 =new MyTask(1,"任務(wù)1");

MyTask task2 =new MyTask(2,"任務(wù)2");

MyTask task3 =new MyTask(3,"任務(wù)3");

MyTask task4 =new MyTask(4,"任務(wù)4");

MyTask task5 =new MyTask(5,"任務(wù)5");

pool.execute(task1);

pool.execute(task2);

pool.execute(task3);

pool.execute(task4);

pool.execute(task5);

八、總結(jié)

1.?線程池中的線程創(chuàng)建時(shí)機(jī)

如果當(dāng)前線程數(shù)少于 corePoolSize,那么提交任務(wù)的時(shí)候創(chuàng)建一個(gè)新的線程,并由這個(gè)線程執(zhí)行這個(gè)任務(wù);

如果當(dāng)前線程數(shù)已經(jīng)達(dá)到 corePoolSize,那么將提交的任務(wù)添加到隊(duì)列中,等待線程池中的線程去隊(duì)列中取任務(wù);

如果隊(duì)列已滿,那么創(chuàng)建新的線程來執(zhí)行任務(wù),需要保證池中的線程數(shù)不會(huì)超過 maximumPoolSize,如果此時(shí)線程數(shù)超過了 maximumPoolSize,那么執(zhí)行拒絕策略。

2. 關(guān)鍵屬性

corePoolSize 到 maximumPoolSize 之間的線程會(huì)被回收,當(dāng)然 corePoolSize 的線程也可以通過設(shè)置而得到回收(allowCoreThreadTimeOut(true))。

workQueue 用于存放任務(wù),添加任務(wù)的時(shí)候,如果當(dāng)前線程數(shù)超過了 corePoolSize,那么往該隊(duì)列中插入任務(wù),線程池中的線程會(huì)負(fù)責(zé)到隊(duì)列中拉取任務(wù)。

keepAliveTime 用于設(shè)置空閑時(shí)間,如果線程數(shù)超出了 corePoolSize,并且有些線程的空閑時(shí)間超過了這個(gè)值,會(huì)執(zhí)行關(guān)閉這些線程的操作

rejectedExecutionHandler 用于處理當(dāng)線程池不能執(zhí)行此任務(wù)時(shí)的情況,默認(rèn)有拋出 RejectedExecutionException 異常、忽略任務(wù)、使用提交任務(wù)的線程來執(zhí)行此任務(wù)將隊(duì)列中等待最久的任務(wù)刪除,然后提交此任務(wù)這四種策略,默認(rèn)為拋出異常。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容