摘要:那么線程池到底是怎么利用類來實(shí)現(xiàn)持續(xù)不斷地接收提交的任務(wù)并執(zhí)行的呢接下來,我們通過的源代碼來一步一步抽絲剝繭,揭開線程池運(yùn)行模型的神秘面紗。
在上一篇文章《從0到1玩轉(zhuǎn)線程池》中,我們了解了線程池的使用方法,以及向線程池中提交任務(wù)的完整流程和ThreadPoolExecutor.execute方法的源代碼。在這篇文章中,我們將會(huì)從頭閱讀線程池ThreadPoolExecutor類的源代碼,深入剖析線程池從提交任務(wù)到執(zhí)行任務(wù)的完整流程,從而建立起完整的線程池運(yùn)行模型。
查看JDK源碼的方式在IDE中,例如IDEA里,我們可以點(diǎn)擊我們樣例代碼里的ThreadPoolExecutor類跳轉(zhuǎn)到JDK中ThreadPoolExecutor類的源代碼。在源代碼中我們可以看到很多java.util.concurrent包的締造者大牛“Doug Lea”所留下的各種注釋,下面的圖片就是該類源代碼的一個(gè)截圖。
這些注釋的內(nèi)容非常有參考價(jià)值,建議有能力的讀者朋友可以自己閱讀一遍。下面,我們就開始閱讀ThreadPoolExecutor的源代碼吧。
控制變量與線程池生命周期在ThreadPoolExecutor類定義的開頭,我們可以看到如下的幾行代碼:
// 控制變量,前3位表示狀態(tài),剩下的數(shù)據(jù)位表示有效的線程數(shù) private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // Integer的位數(shù)減去3位狀態(tài)位就是線程數(shù)的位數(shù) private static final int COUNT_BITS = Integer.SIZE - 3; // CAPACITY就是線程數(shù)的上限(含),即2^COUNT_BITS - 1個(gè) private static final int CAPACITY = (1 << COUNT_BITS) - 1;
第一行是一個(gè)用來作為控制變量的整型值,即一個(gè)Integer。之所以要用AtomicInteger類是因?yàn)橐WC多線程安全,在本系列之后的文章中會(huì)對AtomicInteger進(jìn)行具體介紹。一個(gè)整型一般是32位,但是這里的代碼為了保險(xiǎn)起見,還是使用了Integer.SIZE來表示整型的總位數(shù)。這里的“位”指的是數(shù)據(jù)位(bit),在計(jì)算機(jī)中,8bit = 1字節(jié),1024字節(jié) = 1KB,1024KB = 1MB。每一位都是一個(gè)0或1的數(shù)字,我們?nèi)绻颜拖胂蟪梢粋€(gè)二進(jìn)制(0或1)的數(shù)組,那么一個(gè)Integer就是32個(gè)數(shù)字的數(shù)組。其中,前三個(gè)被用來表示狀態(tài),那么我們就可以表示2^3 = 8個(gè)不同的狀態(tài)了。剩下的29位二進(jìn)制數(shù)字都會(huì)被用于表示當(dāng)前線程池中有效線程的數(shù)量,上限就是(2^29 - 1)個(gè),即常量CAPACITY。
之后的部分列出了線程池的所有狀態(tài):
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;
在這里可以忽略數(shù)字后面的<< COUNT_BITS,可以把狀態(tài)簡單地理解為前面的數(shù)字部分,這樣的簡化基本不影響結(jié)論。
各個(gè)狀態(tài)的解釋如下:
RUNNING,正常運(yùn)行狀態(tài),可以接受新的任務(wù)和處理隊(duì)列中的任務(wù)
SHUTDOWN,關(guān)閉中狀態(tài),不能接受新任務(wù),但是可以處理隊(duì)列中的任務(wù)
STOP,停止中狀態(tài),不能接受新任務(wù),也不處理隊(duì)列中的任務(wù),會(huì)中斷進(jìn)行中的任務(wù)
TIDYING,待結(jié)束狀態(tài),所有任務(wù)已經(jīng)結(jié)束,線程數(shù)歸0,進(jìn)入TIDYING狀態(tài)后將會(huì)運(yùn)行terminated()方法
TERMINATED,結(jié)束狀態(tài),terminated()方法調(diào)用完成后進(jìn)入
這幾個(gè)狀態(tài)所對應(yīng)的數(shù)字值是按照順序排列的,也就是說線程池的狀態(tài)只能從小到大變化,這也方便了通過數(shù)字比較來判斷狀態(tài)所在的階段,這種通過數(shù)字大小來比較狀態(tài)值的方法在ThreadPoolExecutor的源碼中會(huì)有大量的使用。
下圖是這五個(gè)狀態(tài)之間的變化過程:
當(dāng)線程池被創(chuàng)建時(shí)會(huì)處于RUNNING狀態(tài),正常接受和處理任務(wù);
當(dāng)shutdown()方法被直接調(diào)用,或者在線程池對象被GC回收時(shí)通過finalize()方法隱式調(diào)用了shutdown()方法時(shí),線程池會(huì)進(jìn)入SHUTDOWN狀態(tài)。該狀態(tài)下線程池仍然會(huì)繼續(xù)執(zhí)行完阻塞隊(duì)列中的任務(wù),只是不再接受新的任務(wù)了。當(dāng)隊(duì)列中的任務(wù)被執(zhí)行完后,線程池中的線程也會(huì)被回收。當(dāng)隊(duì)列和線程都被清空后,線程池將進(jìn)入TIDYING狀態(tài);
在線程池處于RUNNING或者SHUTDOWN狀態(tài)時(shí),如果有代碼調(diào)用了shutdownNow()方法,則線程池會(huì)進(jìn)入STOP狀態(tài)。在STOP狀態(tài)下,線程池會(huì)直接清空阻塞隊(duì)列中待執(zhí)行的任務(wù),然后中斷所有正在進(jìn)行中的任務(wù)并回收線程。當(dāng)線程都被清空以后,線程池就會(huì)進(jìn)入TIDYING狀態(tài);
當(dāng)線程池進(jìn)入TIDYING狀態(tài)時(shí),將會(huì)運(yùn)行terminated()方法,該方法執(zhí)行完后,線程池就會(huì)進(jìn)入最終的TERMINATED狀態(tài),徹底結(jié)束。
到這里我們就已經(jīng)清楚地了解了線程從剛被創(chuàng)建時(shí)的RUNNING狀態(tài)一直到最終的TERMINATED狀態(tài)的整個(gè)生命周期了。那么當(dāng)我們要向一個(gè)RUNNING狀態(tài)的線程池提交任務(wù)時(shí)會(huì)發(fā)生些什么呢?
execute方法的實(shí)現(xiàn)我們一般會(huì)使用execute方法提交我們的任務(wù),那么線程池在這個(gè)過程中做了什么呢?在ThreadPoolExecutor類的execute()方法的源代碼中,我們主要做了四件事:
如果當(dāng)前線程池中的線程數(shù)小于核心線程數(shù)corePoolSize,則通過threadFactory創(chuàng)建一個(gè)新的線程,并把入?yún)⒅械娜蝿?wù)作為第一個(gè)任務(wù)傳入該線程;
如果當(dāng)前線程池中的線程數(shù)已經(jīng)達(dá)到了核心線程數(shù)corePoolSize,那么就會(huì)通過阻塞隊(duì)列workerQueue的offer方法來將任務(wù)添加到隊(duì)列中保存,并等待線程空閑后進(jìn)行執(zhí)行;
如果線程數(shù)已經(jīng)達(dá)到了corePoolSize且阻塞隊(duì)列中無法插入該任務(wù)(比如已滿),那么線程池就會(huì)再增加一個(gè)線程來執(zhí)行該任務(wù),除非線程數(shù)已經(jīng)達(dá)到了最大線程數(shù)maximumPoolSize;
如果確實(shí)已經(jīng)達(dá)到了最大線程數(shù),那么就會(huì)通過拒絕策略對象handler拒絕這個(gè)任務(wù)。
總體上的執(zhí)行流程如下,下方的黑色同心圓代表流程結(jié)束:
這里解釋一下阻塞隊(duì)列的定義,方便大家閱讀:
線程池中的阻塞隊(duì)列專門用于存放需要等待線程空閑的待執(zhí)行任務(wù),而阻塞隊(duì)列是這樣的一種數(shù)據(jù)結(jié)構(gòu),它是一個(gè)隊(duì)列(類似于一個(gè)List),可以存放0到N個(gè)元素。我們可以對這個(gè)隊(duì)列進(jìn)行插入和彈出元素的操作,彈出操作可以理解為是一個(gè)獲取并從隊(duì)列中刪除一個(gè)元素的操作。當(dāng)隊(duì)列中沒有元素時(shí),對這個(gè)隊(duì)列的獲取操作將會(huì)被阻塞,直到有元素被插入時(shí)才會(huì)被喚醒;當(dāng)隊(duì)列已滿時(shí),對這個(gè)隊(duì)列的插入操作將會(huì)被阻塞,直到有元素被彈出后才會(huì)被喚醒。這樣的一種數(shù)據(jù)結(jié)構(gòu)非常適合于線程池的場景,當(dāng)一個(gè)工作線程沒有任務(wù)可處理時(shí)就會(huì)進(jìn)入阻塞狀態(tài),直到有新任務(wù)提交后才被喚醒。
線程池中常用的阻塞隊(duì)列一般有三種類型:直連隊(duì)列、無界隊(duì)列、有界隊(duì)列。不同的阻塞隊(duì)列類型會(huì)被線程池的行為產(chǎn)生不同的影響,有興趣的讀者可以在上一篇文章《從0到1玩轉(zhuǎn)線程池》中找到不同類型阻塞隊(duì)列的具體解釋。
下面是帶有注釋的源代碼,大家可以和上面的流程對照起來參考一下:
public void execute(Runnable command) { // 檢查提交的任務(wù)是否為空 if (command == null) throw new NullPointerException(); // 獲取控制變量值 int c = ctl.get(); // 檢查當(dāng)前線程數(shù)是否達(dá)到了核心線程數(shù) if (workerCountOf(c) < corePoolSize) { // 未達(dá)到核心線程數(shù),則創(chuàng)建新線程 // 并將傳入的任務(wù)作為該線程的第一個(gè)任務(wù) if (addWorker(command, true)) // 添加線程成功則直接返回,否則繼續(xù)執(zhí)行 return; // 因?yàn)榍懊嬲{(diào)用了耗時(shí)操作addWorker方法 // 所以線程池狀態(tài)有可能發(fā)生了改變,重新獲取狀態(tài)值 c = ctl.get(); } // 判斷線程池當(dāng)前狀態(tài)是否是運(yùn)行中 // 如果是則調(diào)用workQueue.offer方法將任務(wù)放入阻塞隊(duì)列 if (isRunning(c) && workQueue.offer(command)) { // 因?yàn)閳?zhí)行了耗時(shí)操作“放入阻塞隊(duì)列”,所以重新獲取狀態(tài)值 int recheck = ctl.get(); // 如果當(dāng)前狀態(tài)不是運(yùn)行中,則將剛才放入阻塞隊(duì)列的任務(wù)拿出,如果拿出成功,則直接拒絕這個(gè)任務(wù) if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) // 如果線程池中沒有線程了,那就創(chuàng)建一個(gè) addWorker(null, false); } // 如果放入阻塞隊(duì)列失?。ㄈ珀?duì)列已滿),則添加一個(gè)線程 else if (!addWorker(command, false)) // 如果添加線程失?。ㄈ缫呀?jīng)達(dá)到了最大線程數(shù)),則拒絕任務(wù) reject(command); }
從上面的源碼中我們可以知道,當(dāng)一個(gè)任務(wù)被通過ThreadPoolExecutor的execute方法提交到線程池中執(zhí)行時(shí),這個(gè)任務(wù)有可能以兩種方式被執(zhí)行:
直接在創(chuàng)建一個(gè)新的Worker時(shí)被作為第一個(gè)任務(wù)傳入,由這個(gè)新創(chuàng)建的線程來執(zhí)行;
把任務(wù)放入一個(gè)阻塞隊(duì)列,等待線程池中的工作線程Worker撈取任務(wù)進(jìn)行執(zhí)行。
這里的這個(gè)Worker指的就是ThreadPoolExecutor.Worker類,這是一個(gè)ThreadPoolExecutor的內(nèi)部類,用于對基礎(chǔ)線程類Thread進(jìn)行包裝和對線程進(jìn)行管理。那么線程池到底是怎么利用Worker類來實(shí)現(xiàn)持續(xù)不斷地接收提交的任務(wù)并執(zhí)行的呢?接下來,我們通過ThreadPoolExecutor的源代碼來一步一步抽絲剝繭,揭開線程池運(yùn)行模型的神秘面紗。
addWorker方法在上文中的execute方法的代碼中我們可以看到線程池是通過addWorker方法來向線程池中添加新線程的,那么新的線程又是如何運(yùn)行起來的呢?
這里我們暫時(shí)跳過addWorker方法的詳細(xì)源代碼,因?yàn)殡m然這個(gè)方法的代碼行數(shù)較多,但是功能相對比較直接,只是通過new Worker(firstTask)創(chuàng)建了一個(gè)代表線程的Worker對象,然后調(diào)用了這個(gè)對象所包含的Thread對象的start()方法。
我們知道一旦調(diào)用了Thread類的start()方法,則這個(gè)線程就會(huì)開始執(zhí)行創(chuàng)建線程時(shí)傳入的Runnable對象。從下面的Worker類構(gòu)造器源代碼可以看出,Worker類正是把自己(this引用)傳入了線程的構(gòu)造器當(dāng)中,所以這個(gè)線程啟動(dòng)后就會(huì)執(zhí)行Worker類的run()方法了,而在Worker的run()方法中只執(zhí)行了一行很簡單的代碼runWorker(this)。
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() { runWorker(this); }runWorker方法的實(shí)現(xiàn)
我們看到線程池中的線程在啟動(dòng)時(shí)會(huì)調(diào)用對應(yīng)的Worker類的runWorker方法,而這里就是整個(gè)線程池任務(wù)執(zhí)行的核心所在了。runWorker方法中包含有一個(gè)類似無限循環(huán)的while語句,讓worker對象可以一直持續(xù)不斷地執(zhí)行提交到線程池中的新任務(wù)或者等待下一個(gè)新任務(wù)的提交。
大家可以配合代碼上帶有的注釋來理解該方法的具體實(shí)現(xiàn):
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; // 將worker的狀態(tài)重置為正常狀態(tài),因?yàn)閟tate狀態(tài)值在構(gòu)造器中被初始化為-1 w.unlock(); // 通過completedAbruptly變量的值判斷任務(wù)是否正常執(zhí)行完成 boolean completedAbruptly = true; try { // 如果task為null就通過getTask方法獲取阻塞隊(duì)列中的下一個(gè)任務(wù) // getTask方法一般不會(huì)返回null,所以這個(gè)while類似于一個(gè)無限循環(huán) // worker對象就通過這個(gè)方法的持續(xù)運(yùn)行來不斷處理新的任務(wù) while (task != null || (task = getTask()) != null) { // 每一次任務(wù)的執(zhí)行都必須獲取鎖來保證下方臨界區(qū)代碼的線程安全 w.lock(); // 如果狀態(tài)值大于等于STOP(狀態(tài)值是有序的,即STOP、TIDYING、TERMINATED) // 且當(dāng)前線程還沒有被中斷,則主動(dòng)中斷線程 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); // 開始 try { // 執(zhí)行任務(wù)前處理操作,默認(rèn)是一個(gè)空實(shí)現(xiàn) // 在子類中可以通過重寫來改變?nèi)蝿?wù)執(zhí)行前的處理行為 beforeExecute(wt, task); // 通過thrown變量保存任務(wù)執(zhí)行過程中拋出的異常 // 提供給下面finally塊中的afterExecute方法使用 Throwable thrown = null; try { // *** 重要:實(shí)際執(zhí)行任務(wù)的代碼 task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { // 因?yàn)镽unnable接口的run方法中不能拋出Throwable對象 // 所以要包裝成Error對象拋出 thrown = x; throw new Error(x); } finally { // 執(zhí)行任務(wù)后處理操作,默認(rèn)是一個(gè)空實(shí)現(xiàn) // 在子類中可以通過重寫來改變?nèi)蝿?wù)執(zhí)行后的處理行為 afterExecute(task, thrown); } } finally { // 將循環(huán)變量task設(shè)置為null,表示已處理完成 task = null; // 累加當(dāng)前worker已經(jīng)完成的任務(wù)數(shù) w.completedTasks++; // 釋放while體中第一行獲取的鎖 w.unlock(); } } // 將completedAbruptly變量設(shè)置為false,表示任務(wù)正常處理完成 completedAbruptly = false; } finally { // 銷毀當(dāng)前的worker對象,并完成一些諸如完成任務(wù)數(shù)量統(tǒng)計(jì)之類的輔助性工作 // 在線程池當(dāng)前狀態(tài)小于STOP的情況下會(huì)創(chuàng)建一個(gè)新的worker來替換被銷毀的worker processWorkerExit(w, completedAbruptly); } }
在runWorker方法的源代碼中有兩個(gè)比較重要的方法調(diào)用,一個(gè)是while條件中對getTask方法的調(diào)用,一個(gè)是在方法的最后對processWorkerExit方法的調(diào)用。下面是對這兩個(gè)方法更詳細(xì)的解釋。
getTask方法在阻塞隊(duì)列中有待執(zhí)行的任務(wù)時(shí)會(huì)從隊(duì)列中彈出一個(gè)任務(wù)并返回,如果阻塞隊(duì)列為空,那么就會(huì)阻塞等待新的任務(wù)提交到隊(duì)列中直到超時(shí)(在一些配置下會(huì)一直等待而不超時(shí)),如果在超時(shí)之前獲取到了新的任務(wù),那么就會(huì)將這個(gè)任務(wù)作為返回值返回。所以一般getTask方法是不會(huì)返回null的,只會(huì)阻塞等待下一個(gè)任務(wù)并在之后將這個(gè)新任務(wù)作為返回值返回。
當(dāng)getTask方法返回null時(shí)會(huì)導(dǎo)致當(dāng)前Worker退出,當(dāng)前線程被銷毀。在以下情況下getTask方法才會(huì)返回null:
當(dāng)前線程池中的線程數(shù)超過了最大線程數(shù)。這是因?yàn)檫\(yùn)行時(shí)通過調(diào)用setMaximumPoolSize修改了最大線程數(shù)而導(dǎo)致的結(jié)果;
線程池處于STOP狀態(tài)。這種情況下所有線程都應(yīng)該被立即回收銷毀;
線程池處于SHUTDOWN狀態(tài),且阻塞隊(duì)列為空。這種情況下已經(jīng)不會(huì)有新的任務(wù)被提交到阻塞隊(duì)列中了,所以線程應(yīng)該被銷毀;
線程可以被超時(shí)回收的情況下等待新任務(wù)超時(shí)。線程被超時(shí)回收一般有以下兩種情況:
超出核心線程數(shù)部分的線程等待任務(wù)超時(shí)
允許核心線程超時(shí)(線程池配置)的情況下線程等待任務(wù)超時(shí)
processWorkerExit方法會(huì)銷毀當(dāng)前線程對應(yīng)的Worker對象,并執(zhí)行一些累加總處理任務(wù)數(shù)等輔助操作,但在線程池當(dāng)前狀態(tài)小于STOP的情況下會(huì)創(chuàng)建一個(gè)新的Worker來替換被銷毀的Worker。
對getTask和processWorkerExit方法源代碼感興趣的讀者可以閱讀下一節(jié)來具體了解一下,不過跳過這一節(jié)也是完全可以的。
getTask與processWorkerExit方法源代碼以下是getTask與processWorkerExit兩個(gè)方法的帶有中文解釋的源代碼:
private Runnable getTask() { // 通過timeOut變量表示線程是否空閑時(shí)間超時(shí)了 boolean timedOut = false; // 無限循環(huán) for (;;) { // 獲取線程池狀態(tài) int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. // 如果 線程池狀態(tài)>=STOP // 或者 (線程池狀態(tài)==SHUTDOWN && 阻塞隊(duì)列為空) // 則直接減少一個(gè)worker計(jì)數(shù)并返回null(返回null會(huì)導(dǎo)致當(dāng)前worker被銷毀) if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } // 獲取線程池中的worker計(jì)數(shù) int wc = workerCountOf(c); // 判斷當(dāng)前線程是否會(huì)被超時(shí)銷毀 // 會(huì)被超時(shí)銷毀的情況:線程池允許核心線程超時(shí) 或 當(dāng)前線程數(shù)大于核心線程數(shù) boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 如果 (當(dāng)前線程數(shù)大于最大線程數(shù) 或 (允許超時(shí)銷毀 且 當(dāng)前發(fā)生了空閑時(shí)間超時(shí))) // 且 (當(dāng)前線程數(shù)大于1 或 阻塞隊(duì)列為空) —— 該條件在阻塞隊(duì)列不為空的情況下保證至少會(huì)保留一個(gè)線程繼續(xù)處理任務(wù) // 則 減少worker計(jì)數(shù)并返回null(返回null會(huì)導(dǎo)致當(dāng)前worker被銷毀) if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 從阻塞隊(duì)列中取出一個(gè)任務(wù)(如果隊(duì)列為空會(huì)進(jìn)入阻塞等待狀態(tài)) // 如果允許空閑超時(shí)銷毀線程的話則帶有一個(gè)等待的超時(shí)時(shí)間 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); // 如果獲取到了任務(wù)就直接返回該任務(wù),返回后會(huì)開始執(zhí)行該任務(wù) if (r != null) return r; // 如果任務(wù)為null,則說明發(fā)生了等待超時(shí),將空閑時(shí)間超時(shí)標(biāo)志設(shè)置為true timedOut = true; } catch (InterruptedException retry) { // 如果等待被中斷了,那說明空閑時(shí)間(等待任務(wù)的時(shí)間)還沒有超時(shí) timedOut = false; } } }
processWorkerExit方法的源代碼:
private void processWorkerExit(Worker w, boolean completedAbruptly) { // 如果completedAbruptly為true則表示任務(wù)執(zhí)行過程中拋出了未處理的異常 // 所以還沒有正確地減少worker計(jì)數(shù),這里需要減少一次worker計(jì)數(shù) if (completedAbruptly) decrementWorkerCount(); // 獲取線程池的主鎖 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 把將被銷毀的線程已完成的任務(wù)數(shù)累計(jì)到線程池的完成任務(wù)總數(shù)上 completedTaskCount += w.completedTasks; // 從worker集合中去掉將會(huì)銷毀的worker workers.remove(w); } finally { // 釋放線程池主鎖 mainLock.unlock(); } // 嘗試結(jié)束線程池 // 這里是為了在關(guān)閉線程池時(shí)等到所有worker都被回收后再結(jié)束線程池 tryTerminate(); int c = ctl.get(); // 如果線程池狀態(tài) < STOP,即RUNNING或SHUTDOWN // 則需要考慮創(chuàng)建新線程來代替被銷毀的線程 if (runStateLessThan(c, STOP)) { // 如果worker是正常執(zhí)行完的,則要判斷一下是否已經(jīng)滿足了最小線程數(shù)要求 // 否則直接創(chuàng)建替代線程 if (!completedAbruptly) { // 如果允許核心線程超時(shí)則最小線程數(shù)是0,否則最小線程數(shù)等于核心線程數(shù) int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 如果阻塞隊(duì)列非空,則至少要有一個(gè)線程繼續(xù)執(zhí)行剩下的任務(wù) if (min == 0 && ! workQueue.isEmpty()) min = 1; // 如果當(dāng)前線程數(shù)已經(jīng)滿足最小線程數(shù)要求 // 那么就不創(chuàng)建替代線程了 if (workerCountOf(c) >= min) return; } // 重新創(chuàng)建一個(gè)worker來代替被銷毀的線程 addWorker(null, false); } }總結(jié)
到這里我們的線程池源代碼之旅就結(jié)束了,在這篇文章中我們首先了解了線程池中的控制變量與狀態(tài)變換流程,之后我們通過線程池的源代碼深入解析了從提交任務(wù)到執(zhí)行任務(wù)的全過程,相信通過這些知識我們已經(jīng)可以在腦海中建立起一套完整的線程池運(yùn)行模型了。如果大家有一些細(xì)節(jié)感覺還不是特別清晰的話,建議不妨再返回到文章的開頭多讀幾遍,相信第二遍的閱讀能給大家?guī)聿灰粯拥捏w驗(yàn),因?yàn)槲易约阂彩窃诘谌巫xThreadPoolExecutor類的源代碼時(shí)才真正打通了其中的一些重要關(guān)節(jié)的。
引子在瀏覽ThreadPoolExexutor源碼的過程中,有幾個(gè)點(diǎn)我們其實(shí)并沒有完全說清楚,比如對鎖的加鎖操作、對控制變量的多次獲取、控制變量的AtomicInteger類型。在下一篇文章中,我將會(huì)介紹這些以鎖、volatile變量、CAS操作、AQS抽象類為代表的一系列線程同步方法,歡迎感興趣的讀者繼續(xù)關(guān)注我后續(xù)發(fā)布的文章~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73889.html
摘要:在這個(gè)范圍廣大的并發(fā)技術(shù)領(lǐng)域當(dāng)中多線程編程可以說是基礎(chǔ)和核心,大多數(shù)抽象并發(fā)問題的構(gòu)思與解決都是基于多線程模型來進(jìn)行的。一般來說,多線程程序會(huì)面臨三類問題正確性問題效率問題死鎖問題。 多線程編程或者說范圍更大的并發(fā)編程是一種非常復(fù)雜且容易出錯(cuò)的編程方式,但是我們?yōu)槭裁催€要冒著風(fēng)險(xiǎn)艱辛地學(xué)習(xí)各種多線程編程技術(shù)、解決各種并發(fā)問題呢? 因?yàn)椴l(fā)是整個(gè)分布式集群的基礎(chǔ),通過分布式集群不僅可以大...
摘要:前言本文內(nèi)容基本摘抄自深入理解虛擬機(jī),以供復(fù)習(xí)之用,沒有多少參考價(jià)值。此區(qū)域是唯一一個(gè)在虛擬機(jī)規(guī)范中沒有規(guī)定任何情況的區(qū)域。堆是所有線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。虛擬機(jī)上把方法區(qū)稱為永久代。 前言 本文內(nèi)容基本摘抄自《深入理解Java虛擬機(jī)》,以供復(fù)習(xí)之用,沒有多少參考價(jià)值。想要更詳細(xì)了解請參考原書。 第二章 1.運(yùn)行時(shí)數(shù)據(jù)區(qū)域 showImg(https://segment...
摘要:面向?qū)ο蟮姆植际脚老x框架一簡介概述是一個(gè)面向?qū)ο蟮姆植际脚老x框架。分布式集群集群方式維護(hù)爬蟲爬蟲運(yùn)行數(shù)據(jù),可通過或定制實(shí)現(xiàn)。 《面向?qū)ο蟮姆植际脚老x框架XXL-CRAWLER》 showImg(https://segmentfault.com/img/remote/1460000011842697);showImg(https://segmentfault.com/img/remote...
摘要:為程序員金三銀四精心挑選的余道面試題與答案,歡迎大家向我推薦你在面試過程中遇到的問題我會(huì)把大家推薦的問題添加到下面的常用面試題清單中供大家參考。 為Java程序員金三銀四精心挑選的300余道Java面試題與答案,歡迎大家向我推薦你在面試過程中遇到的問題,我會(huì)把大家推薦的問題添加到下面的常用面試題清單中供大家參考。 前兩天寫的以下博客,大家比較認(rèn)可,熱度不錯(cuò),希望可以幫到準(zhǔn)備或者正在參加...
閱讀 5072·2021-09-07 09:58
閱讀 796·2019-08-30 15:55
閱讀 2935·2019-08-30 15:55
閱讀 927·2019-08-30 15:53
閱讀 1562·2019-08-29 12:57
閱讀 1829·2019-08-26 13:46
閱讀 571·2019-08-26 11:00
閱讀 3668·2019-08-23 15:42