詳解Java線程池如何實(shí)現(xiàn)優(yōu)雅退出
在【高并發(fā)專題】中,我們從源碼角度深度分析了線程池中那些重要的接口和抽象類、深度解析了線程池是如何創(chuàng)建的,ThreadPoolExecutor類有哪些屬性和內(nèi)部類,以及它們對(duì)線程池的重要作用。深度分析了線程池的整體核心流程,以及如何拆解Worker線程的執(zhí)行代碼,深度解析Worker線程的執(zhí)行流程。
本文,我們就來從源碼角度深度解析線程池是如何優(yōu)雅的退出程序的。首先,我們來看下ThreadPoolExecutor類中的shutdown()方法。
shutdown()方法
當(dāng)使用線程池的時(shí)候,調(diào)用了shutdown()方法后,線程池就不會(huì)再接受新的執(zhí)行任務(wù)了。但是在調(diào)用shutdown()方法之前放入任務(wù)隊(duì)列中的任務(wù)還是要執(zhí)行的。此方法是非阻塞方法,調(diào)用后會(huì)立即返回,并不會(huì)等待任務(wù)隊(duì)列中的任務(wù)全部執(zhí)行完畢后再返回。我們看下shutdown()方法的源代碼,如下所示。
public void shutdown() { //獲取線程池的全局鎖 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //檢查是否有關(guān)閉線程池的權(quán)限 checkShutdownAccess(); //將當(dāng)前線程池的狀態(tài)設(shè)置為SHUTDOWN advanceRunState(SHUTDOWN); //中斷Worker線程 interruptIdleWorkers(); //為ScheduledThreadPoolExecutor調(diào)用鉤子函數(shù) onShutdown(); // hook for } finally { //釋放線程池的全局鎖 mainLock.unlock(); } //嘗試將狀態(tài)變?yōu)門ERMINATED tryTerminate(); }
總體來說,shutdown()方法的代碼比較簡單,首先檢查了是否有權(quán)限來關(guān)閉線程池,如果有權(quán)限,則再次檢測是否有中斷工作線程的權(quán)限,如果沒有權(quán)限,則會(huì)拋出SecurityException異常,代碼如下所示。
//檢查是否有關(guān)閉線程池的權(quán)限 checkShutdownAccess(); //將當(dāng)前線程池的狀態(tài)設(shè)置為SHUTDOWN advanceRunState(SHUTDOWN); //中斷Worker線程 interruptIdleWorkers();
其中,checkShutdownAccess()方法的實(shí)現(xiàn)代碼如下所示。
private void checkShutdownAccess() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission(shutdownPerm); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) security.checkAccess(w.thread); } finally { mainLock.unlock(); } } }
對(duì)于checkShutdownAccess()方法的代碼理解起來比較簡單,就是檢測是否具有關(guān)閉線程池的權(quán)限,期間使用了線程池的全局鎖。
接下來,我們看advanceRunState(int)方法的源代碼,如下所示。
private void advanceRunState(int targetState) { for (;;) { int c = ctl.get(); if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) break; } }
advanceRunState(int)方法的整體邏輯就是:判斷當(dāng)前線程池的狀態(tài)是否為指定的狀態(tài),在shutdown()方法中傳遞的狀態(tài)是SHUTDOWN,如果是SHUTDOWN,則直接返回;如果不是SHUTDOWN,則將當(dāng)前線程池的狀態(tài)設(shè)置為SHUTDOWN。
接下來,我們看看showdown()方法調(diào)用的interruptIdleWorkers()方法,如下所示。
private void interruptIdleWorkers() { interruptIdleWorkers(false); }
可以看到,interruptIdleWorkers()方法調(diào)用的是interruptIdleWorkers(boolean)方法,繼續(xù)看interruptIdleWorkers(boolean)方法的源代碼,如下所示。
private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
上述代碼的總體邏輯為:獲取線程池的全局鎖,循環(huán)所有的工作線程,檢測線程是否被中斷,如果沒有被中斷,并且Worker線程獲得了鎖,則執(zhí)行線程的中斷方法,并釋放線程獲取到的鎖。此時(shí)如果onlyOne參數(shù)為true,則退出循環(huán)。否則,循環(huán)所有的工作線程,執(zhí)行相同的操作。最終,釋放線程池的全局鎖。
接下來,我們看下shutdownNow()方法。
shutdownNow()方法
如果調(diào)用了線程池的shutdownNow()方法,則線程池不會(huì)再接受新的執(zhí)行任務(wù),也會(huì)將任務(wù)隊(duì)列中存在的任務(wù)丟棄,正在執(zhí)行的Worker線程也會(huì)被立即中斷,同時(shí),方法會(huì)立刻返回,此方法存在一個(gè)返回值,也就是當(dāng)前任務(wù)隊(duì)列中被丟棄的任務(wù)列表。
shutdownNow()方法的源代碼如下所示。
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //檢查是否有關(guān)閉權(quán)限 checkShutdownAccess(); //設(shè)置線程池的狀態(tài)為STOP advanceRunState(STOP); //中斷所有的Worker線程 interruptWorkers(); //將任務(wù)隊(duì)列中的任務(wù)移動(dòng)到tasks集合中 tasks = drainQueue(); } finally { mainLock.unlock(); } /嘗試將狀態(tài)變?yōu)門ERMINATED tryTerminate(); //返回tasks集合 return tasks; }
shutdownNow()方法的源代碼的總體邏輯與shutdown()方法基本相同,只是shutdownNow()方法將線程池的狀態(tài)設(shè)置為STOP,中斷所有的Worker線程,并且將任務(wù)隊(duì)列中的所有任務(wù)移動(dòng)到tasks集合中并返回。
可以看到,shutdownNow()方法中斷所有的線程時(shí),調(diào)用了interruptWorkers()方法,接下來,我們就看下interruptWorkers()方法的源代碼,如下所示。
private void interruptWorkers() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) w.interruptIfStarted(); } finally { mainLock.unlock(); } }
interruptWorkers()方法的邏輯比較簡單,就是獲得線程池的全局鎖,循環(huán)所有的工作線程,依次中斷線程,最后釋放線程池的全局鎖。
在interruptWorkers()方法的內(nèi)部,實(shí)際上調(diào)用的是Worker類的interruptIfStarted()方法來中斷線程,我們看下Worker類的interruptIfStarted()方法的源代碼,如下所示。
void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } }
發(fā)現(xiàn)其本質(zhì)上調(diào)用的還是Thread類的interrupt()方法來中斷線程。
awaitTermination(long, TimeUnit)方法
當(dāng)線程池調(diào)用了awaitTermination(long, TimeUnit)方法后,會(huì)阻塞調(diào)用者所在的線程,直到線程池的狀態(tài)修改為TERMINATED才返回,或者達(dá)到了超時(shí)時(shí)間返回。接下來,我們看下awaitTermination(long, TimeUnit)方法的源代碼,如下所示。
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { //獲取距離超時(shí)時(shí)間剩余的時(shí)長 long nanos = unit.toNanos(timeout); //獲取Worker線程的的全局鎖 final ReentrantLock mainLock = this.mainLock; //加鎖 mainLock.lock(); try { for (;;) { //當(dāng)前線程池狀態(tài)為TERMINATED狀態(tài),會(huì)返回true if (runStateAtLeast(ctl.get(), TERMINATED)) return true; //達(dá)到超時(shí)時(shí)間,已超時(shí),則返回false if (nanos <= 0) return false; //重置距離超時(shí)時(shí)間的剩余時(shí)長 nanos = termination.awaitNanos(nanos); } } finally { //釋放鎖 mainLock.unlock(); } }
上述代碼的總體邏輯為:首先獲取Worker線程的獨(dú)占鎖,后在循環(huán)判斷當(dāng)前線程池是否已經(jīng)是TERMINATED狀態(tài),如果是則直接返回true,否則檢測是否已經(jīng)超時(shí),如果已經(jīng)超時(shí),則返回false。如果未超時(shí),則重置距離超時(shí)時(shí)間的剩余時(shí)長。接下來,進(jìn)入下一輪循環(huán),再次檢測當(dāng)前線程池是否已經(jīng)是TERMINATED狀態(tài),如果是則直接返回true,否則檢測是否已經(jīng)超時(shí),如果已經(jīng)超時(shí),則返回false。如果未超時(shí),則重置距離超時(shí)時(shí)間的剩余時(shí)長。以此循環(huán),直到線程池的狀態(tài)變?yōu)門ERMINATED或者已經(jīng)超時(shí)。
到此這篇關(guān)于詳解Java線程池如何實(shí)現(xiàn)優(yōu)雅退出的文章就介紹到這了,更多相關(guān)Java線程池退出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA快速部署Spring?Boot?項(xiàng)目到Docker的實(shí)現(xiàn)方法
本文主要介紹了IDEA快速部署Spring?Boot?項(xiàng)目到Docker的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07SpringBoot中@Insert、@Update實(shí)現(xiàn)批量新增更新的使用示例
本文主要介紹了SpringBoot中@Insert、@Update實(shí)現(xiàn)批量新增更新的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-10-10centos7如何通過systemctl啟動(dòng)springboot服務(wù)代替java -jar方式啟動(dòng)
這篇文章主要介紹了centos7如何通過systemctl啟動(dòng)springboot服務(wù)代替java -jar方式啟動(dòng),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01springBoot項(xiàng)目中使用@Value取值出現(xiàn)的問題及解決
這篇文章主要介紹了springBoot項(xiàng)目中使用@Value取值出現(xiàn)的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Java如何通過枚舉實(shí)現(xiàn)有限狀態(tài)機(jī)
這篇文章主要介紹了Java如何通過枚舉實(shí)現(xiàn)有限狀態(tài)機(jī),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07KafkaListener注解的實(shí)現(xiàn)機(jī)制源碼解析
這篇文章主要為大家介紹了KafkaListener注解的實(shí)現(xiàn)機(jī)制源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10