原文:https://www.cnblogs.com/qingquanzi/p/9018627.html
本篇就以ThreadPoolExecutor為例届宠,來介紹下如何優(yōu)雅的關(guān)閉線程池圃泡。
01 線程中斷
在介紹線程池關(guān)閉之前撬槽,先介紹下Thread的interrupt逾一。
在程序中侦铜,我們是不能隨便中斷一個線程的先鱼,因為這是極其不安全的操作辟躏,我們無法知道這個線程正運行在什么狀態(tài)君纫,它可能持有某把鎖驯遇,強行中斷可能導(dǎo)致鎖不能釋放的問題;或者線程可能在操作數(shù)據(jù)庫蓄髓,強行中斷導(dǎo)致數(shù)據(jù)不一致混亂的問題叉庐。正因此,JAVA里將Thread的stop方法設(shè)置為過時会喝,以禁止大家使用陡叠。
一個線程什么時候可以退出呢?當然只有線程自己才能知道肢执。
所以我們這里要說的Thread的interrrupt方法枉阵,本質(zhì)不是用來中斷一個線程。是將線程設(shè)置一個中斷狀態(tài)预茄。
當我們調(diào)用線程的interrupt方法兴溜,它有兩個作用:
1、如果此線程處于阻塞狀態(tài)(比如調(diào)用了wait方法耻陕,io等待)拙徽,則會立馬退出阻塞,并拋出InterruptedException異常淮蜈,線程就可以通過捕獲InterruptedException來做一定的處理斋攀,然后讓線程退出。
2梧田、如果此線程正處于運行之中淳蔼,則線程不受任何影響侧蘸,繼續(xù)運行,僅僅是線程的中斷標記被設(shè)置為true鹉梨。所以線程要在適當?shù)奈恢猛ㄟ^調(diào)用isInterrupted方法來查看自己是否被中斷讳癌,并做退出操作。
注:
如果線程的interrupt方法先被調(diào)用存皂,然后線程調(diào)用阻塞方法進入阻塞狀態(tài)晌坤,InterruptedException異常依舊會拋出。
如果線程捕獲InterruptedException異常后旦袋,繼續(xù)調(diào)用阻塞方法骤菠,將不再觸發(fā)InterruptedException異常。
02 線程池的關(guān)閉
線程池提供了兩個關(guān)閉方法疤孕,shutdownNow和shuwdown方法商乎。
shutdownNow方法的解釋是:線程池拒接收新提交的任務(wù),同時立馬關(guān)閉線程池祭阀,線程池里的任務(wù)不再執(zhí)行鹉戚。
shutdown方法的解釋是:線程池拒接收新提交的任務(wù),同時等待線程池里的任務(wù)執(zhí)行完畢后關(guān)閉線程池专控。
以上的說法雖然沒錯抹凳,但是還有很多的細節(jié),比如調(diào)用shutdown方法后伦腐,正在執(zhí)行任務(wù)的線程做出什么反應(yīng)赢底?正在等待任務(wù)的線程又做出什么反應(yīng)?線程在什么情況下才會徹底退出蔗牡。如果不了解這些細節(jié)颖系,在關(guān)閉線程池時就難免遇到,像線程池關(guān)閉不了辩越,關(guān)閉線程池出現(xiàn)報錯等情況嘁扼。
再說這些關(guān)閉線程池細節(jié)之前,需要強調(diào)一點的是黔攒,調(diào)用完shutdownNow和shuwdown方法后趁啸,并不代表線程池已經(jīng)完成關(guān)閉操作,它只是異步的通知線程池進行關(guān)閉處理督惰。如果要同步等待線程池徹底關(guān)閉后才繼續(xù)往下執(zhí)行不傅,需要調(diào)用awaitTermination方法進行同步等待。
有了以上介紹赏胚,下面就結(jié)合線程池源碼访娶,分別說說這兩個線程池關(guān)閉方法的一些實現(xiàn)細節(jié)。
shutdownNow
我們看一下shutdownNow方法的源碼:
在shutdownNow方法里觉阅,重要的三句代碼我用紅色數(shù)字標出來了崖疤。
第一句就是原子性的修改線程池的狀態(tài)為STOP狀態(tài)(比較簡單我就不貼代碼了)
第三句是將隊列里還沒有執(zhí)行的任務(wù)放到列表里秘车,返回給調(diào)用方。
第二句是遍歷線程池里的所有工作線程劫哼,然后調(diào)用線程的interrupt方法叮趴。如下圖:
以上就是shutdownNow方法的執(zhí)行邏輯:將線程池狀態(tài)修改為STOP,然后調(diào)用線程池里的所有線程的interrupt方法权烧。
調(diào)用shutdownNow后眯亦,線程池里的線程會做如何反應(yīng)呢?那就要看般码,線程池里線程正在執(zhí)行的代碼邏輯了妻率。其在線程池的runWorker方法里(對線程池的執(zhí)行原理不了解的,請看之前的文章)侈询,其代碼如下:
正常情況下舌涨,線程池里的線程,就是在這個while循環(huán)里不停地執(zhí)行扔字。其中代碼task.run()就是在執(zhí)行我們提交給線程池的任務(wù),如當我們調(diào)用shutdownNow時温技,task.run()里面正處于IO阻塞革为,則會導(dǎo)致報錯,如果task.run()里正在正常執(zhí)行舵鳞,則不受影響震檩,繼續(xù)執(zhí)行完這個任務(wù)。
從上圖看的出來蜓堕,如果getTask()方法返回null,也會導(dǎo)致線程的退出抛虏。我們再來看看getTask方法的實現(xiàn):
如果我們調(diào)用shutdownNow方法時,線程處于從隊列里讀取任務(wù)而阻塞中(圖中下邊的紅框)套才,則會導(dǎo)致拋出InterruptedException異常迂猴,但因為異常被捕獲,線程將會繼續(xù)在這個for循環(huán)里執(zhí)行背伴。
還記得shutdownNow方法里將線程修改為STOP狀態(tài)吧沸毁,當執(zhí)行到上邊紅框里的代碼時,由于STOP狀態(tài)值是大于SHUTDOWN狀態(tài)傻寂,STOP也大于等于STOP息尺,不管任務(wù)隊列是否為空,都會進入if語句從而返回null,線程退出疾掰。
總結(jié):
當我們調(diào)用線程池的shutdownNow時搂誉,
如果線程正在getTask方法中執(zhí)行,則會通過for循環(huán)進入到if語句静檬,于是getTask返回null,從而線程退出炭懊。不管線程池里是否有未完成的任務(wù)并级。
如果線程因為執(zhí)行提交到線程池里的任務(wù)而處于阻塞狀態(tài),則會導(dǎo)致報錯(如果任務(wù)里沒有捕獲InterruptedException異常)凛虽,否則線程會執(zhí)行完當前任務(wù)死遭,然后通過getTask方法返回為null來退出。
shutdown
我們再來看看shutdown方法的源碼:
跟shutdownNow類似凯旋,只不過它是將線程池的狀態(tài)修改為SHUTDOWN狀態(tài)呀潭,然后調(diào)用interruptIdleWorkers方法,來中斷空閑的線程至非。這是interruptIdleWorkers方法的實現(xiàn):
跟shutdownNow方法調(diào)用interruptWorkers方法不同的是钠署,interruptIdleWorkers方法在遍歷線程池里的線程時,有一個w.tryLock()加鎖判斷荒椭,只有加鎖成功的線程才會被調(diào)用interrupt方法谐鼎。那什么情況下才能被加鎖成功?什么情況下不能被加鎖成功呢?這就需要我們繼續(xù)回到線程執(zhí)行的runWorker方法趣惠。
在上邊runWorker方法代碼的截圖中狸棍,我刻意將w.lock()和w.unlock()調(diào)用用紅框圈起。其實就是正運行在w.lock和w.unlock之間的線程將因為加鎖失敗味悄,而不會被調(diào)用interrupt方法草戈,換句話說,就是正在執(zhí)行線程池里任務(wù)的線程不會被中斷侍瑟。
不管是被調(diào)用了interrupt的線程還是沒被調(diào)用的線程唐片,什么時候退出呢?涨颜,這就要看getTask方法的返回是否為null了费韭。
在getTask里的if判斷(上文中g(shù)etTask代碼截圖中上邊紅色方框的代碼)中,由于線程池被shutdown方法修改為SHUTDOWN狀態(tài)庭瑰,SHUTDOWN大于等于SHUTDOWN成立沒問題星持,但是SHUTDOWN不在大于等于STOP狀態(tài),所以只有隊列為空见擦,getTask方法才會返回null钉汗,導(dǎo)致線程退出。
總結(jié):
當我們調(diào)用線程池的shuwdown方法時鲤屡,
如果線程正在執(zhí)行線程池里的任務(wù)损痰,即便任務(wù)處于阻塞狀態(tài),線程也不會被中斷酒来,而是繼續(xù)執(zhí)行卢未。
如果線程池阻塞等待從隊列里讀取任務(wù),則會被喚醒,但是會繼續(xù)判斷隊列是否為空辽社,如果不為空會繼續(xù)從隊列里讀取任務(wù)伟墙,為空則線程退出。
03 優(yōu)雅的關(guān)閉線程池
有了上邊對兩個關(guān)閉線程池方法的了解滴铅,相信優(yōu)雅安全關(guān)閉線程池將不再是問題戳葵。
我們知道,使用shutdownNow方法汉匙,可能會引起報錯拱烁,使用shutdown方法可能會導(dǎo)致線程關(guān)閉不了。
所以當我們使用shutdownNow方法關(guān)閉線程池時噩翠,一定要對任務(wù)里進行異常捕獲戏自。
當我們使用shuwdown方法關(guān)閉線程池時,一定要確保任務(wù)里不會有永久阻塞等待的邏輯伤锚,否則線程池就關(guān)閉不了擅笔。
最后,一定要記得屯援,shutdownNow和shuwdown調(diào)用完猛们,線程池并不是立馬就關(guān)閉了,要想等待線程池關(guān)閉狞洋,還需調(diào)用awaitTermination方法來阻塞等待阅懦。