线程的退出 java_(转)Java结束线程的三种方法
背景:面試過程中問到結(jié)束線程的方法和線程池shutdown shutdownnow區(qū)別以及底層的實現(xiàn),當(dāng)時答的并不好。
線程屬于一次性消耗品,在執(zhí)行完run()方法之后線程便會正常結(jié)束了,線程結(jié)束后便會銷毀,不能再次start,只能重新建立新的線程對象,但有時run()方法是永遠(yuǎn)不會結(jié)束的。例如在程序中使用線程進(jìn)行Socket監(jiān)聽請求,或是其他的需要循環(huán)處理的任務(wù)。在這種情況下,一般是將這些任務(wù)放在一個循環(huán)中,如while循環(huán)。當(dāng)需要結(jié)束線程時,如何退出線程呢?
有三種方法可以結(jié)束線程:
1.設(shè)置退出標(biāo)志,使線程正常退出,也就是當(dāng)run()方法完成后線程終止
2.使用interrupt()方法中斷線程
3.使用stop方法強(qiáng)行終止線程(不推薦使用,Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 這些終止線程運行的方法已經(jīng)被廢棄,使用它們是極端不安全的!)
前兩種方法都可以實現(xiàn)線程的正常退出;第3種方法相當(dāng)于電腦斷電關(guān)機(jī)一樣,是不安全的方法。
1.使用退出標(biāo)志終止線程
一般run()方法執(zhí)行完,線程就會正常結(jié)束,然而,常常有些線程是伺服線程。它們需要長時間的運行,只有在外部某些條件滿足的情況下,才能關(guān)閉這些線程。使用一個變量來控制循環(huán),例如:最直接的方法就是設(shè)一個boolean類型的標(biāo)志,并通過設(shè)置這個標(biāo)志為true或false來控制while循環(huán)是否退出,代碼示例:
public class ThreadSafe extendsThread {public volatile boolean exit = false;public voidrun() {while (!exit){//do something
}
}
}
定義了一個退出標(biāo)志exit,當(dāng)exit為true時,while循環(huán)退出,exit的默認(rèn)值為false.在定義exit時,使用了一個Java關(guān)鍵字volatile,這個關(guān)鍵字的目的是使exit同步,也就是說在同一時刻只能由一個線程來修改exit的值.
2.使用interrupt()方法中斷當(dāng)前線程
使用interrupt()方法來中斷線程有兩種情況:
1.線程處于阻塞狀態(tài),如使用了sleep,同步鎖的wait,socket中的receiver,accept等方法時,會使線程處于阻塞狀態(tài)。當(dāng)調(diào)用線程的interrupt()方法時,會拋出InterruptException異常。阻塞中的那個方法拋出這個異常,通過代碼捕獲該異常,然后break跳出循環(huán)狀態(tài),從而讓我們有機(jī)會結(jié)束這個線程的執(zhí)行。通常很多人認(rèn)為只要調(diào)用interrupt方法線程就會結(jié)束,實際上是錯的, 一定要先捕獲InterruptedException異常之后通過break來跳出循環(huán),才能正常結(jié)束run方法。
代碼示例:
public class ThreadSafe extendsThread {public voidrun() {while (true){try{
Thread.sleep(5*1000);//阻塞5妙
}catch(InterruptedException e){
e.printStackTrace();break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)。
}
}
}
}
2.線程未處于阻塞狀態(tài),使用isInterrupted()判斷線程的中斷標(biāo)志來退出循環(huán)。當(dāng)使用interrupt()方法時,中斷標(biāo)志就會置true,和使用自定義的標(biāo)志來控制循環(huán)是一樣的道理。
代碼示例:
public class ThreadSafe extendsThread {public voidrun() {while (!isInterrupted()){//do something, but no throw InterruptedException
}
}
}
為什么要區(qū)分進(jìn)入阻塞狀態(tài)和和非阻塞狀態(tài)兩種情況了,是因為當(dāng)阻塞狀態(tài)時,如果有interrupt()發(fā)生,系統(tǒng)除了會拋出InterruptedException異常外,還會調(diào)用interrupted()函數(shù),調(diào)用時能獲取到中斷狀態(tài)是true的狀態(tài),調(diào)用完之后會復(fù)位中斷狀態(tài)為false,所以異常拋出之后通過isInterrupted()是獲取不到中斷狀態(tài)是true的狀態(tài),從而不能退出循環(huán),因此在線程未進(jìn)入阻塞的代碼段時是可以通過isInterrupted()來判斷中斷是否發(fā)生來控制循環(huán),在進(jìn)入阻塞狀態(tài)后要通過捕獲異常來退出循環(huán)。因此使用interrupt()來退出線程的最好的方式應(yīng)該是兩種情況都要考慮:
代碼示例:
public class ThreadSafe extendsThread {public voidrun() {while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標(biāo)志來退出
try{
Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
}catch(InterruptedException e){
e.printStackTrace();break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)。
}
}
}
}
3.使用stop方法終止線程
程序中可以直接使用thread.stop()來強(qiáng)行終止線程,但是stop方法是很危險的,就象突然關(guān)閉計算機(jī)電源,而不是按正常程序關(guān)機(jī)一樣,可能會產(chǎn)生不可預(yù)料的結(jié)果,不安全主要是:thread.stop()調(diào)用之后,創(chuàng)建子線程的線程就會拋出ThreadDeatherror的錯誤,并且會釋放子線程所持有的所有鎖。一般任何進(jìn)行加鎖的代碼塊,都是為了保護(hù)數(shù)據(jù)的一致性,如果在調(diào)用thread.stop()后導(dǎo)致了該線程所持有的所有鎖的突然釋放(不可控制),那么被保護(hù)數(shù)據(jù)就有可能呈現(xiàn)不一致性,其他線程在使用這些被破壞的數(shù)據(jù)時,有可能導(dǎo)致一些很奇怪的應(yīng)用程序錯誤。因此,并不推薦使用stop方法來終止線程。
interrupt、interrupted 、isInterrupted 區(qū)別
1、interrupt
interrupt方法用于中斷線程。調(diào)用該方法的線程的狀態(tài)為將被置為"中斷"狀態(tài)。
注意:線程中斷僅僅是置線程的中斷狀態(tài)位,不會停止線程。需要用戶自己去監(jiān)視線程的狀態(tài)為并做處理。支持線程中斷的方法(也就是線程中斷后會拋出interruptedException的方法)就是在監(jiān)視線程的中斷狀態(tài),一旦線程的中斷狀態(tài)被置為“中斷狀態(tài)”,就會拋出中斷異常。
public voidinterrupt() {if (this !=Thread.currentThread())
checkAccess();synchronized(blockerLock) {
Interruptible b=blocker;if (b != null) {
interrupt0();//Just to set the interrupt flag
b.interrupt(this);return;
}
}
interrupt0();
}private native void interrupt0();
interrupt 設(shè)置中斷狀態(tài)。
2、interrupted 和?isInterrupted
首先看一下該方法的實現(xiàn):
public static booleaninterrupted() {return currentThread().isInterrupted(true);
}
該方法就是直接調(diào)用當(dāng)前線程的isInterrupted(true)方法。
靜態(tài)方法,返回當(dāng)前的線程的狀態(tài),并會清楚之前的狀態(tài)。
然后再來看一下 isInterrupted的實現(xiàn):
public booleanisInterrupted() {return isInterrupted(false);
}
只會返回當(dāng)前線程的狀態(tài),并不會清除。
底層都是調(diào)用一個本地方法:
/*** Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.*/
private native boolean isInterrupted(boolean ClearInterrupted);
原來這是一個本地方法,看不到源碼。不過沒關(guān)系,通過參數(shù)名我們就能知道,這個參數(shù)代表是否要清除狀態(tài)位。
如果這個參數(shù)為true,說明返回線程的狀態(tài)位后,要清掉原來的狀態(tài)位(恢復(fù)成原來情況)。這個參數(shù)為false,就是直接返回線程的狀態(tài)位。
這兩個方法很好區(qū)分,只有當(dāng)前線程才能清除自己的中斷位(對應(yīng)interrupted()方法)
interrupted 和?isInterrupted
這兩個方法有兩個主要區(qū)別:
interrupted 是作用于當(dāng)前線程,isInterrupted 是作用于調(diào)用該方法的線程對象所對應(yīng)的線程。(線程對象對應(yīng)的線程不一定是當(dāng)前運行的線程。例如我們可以在A線程中去調(diào)用B線程對象的isInterrupted方法。)
這兩個方法最終都會調(diào)用同一個方法,只不過參數(shù)一個是true,一個是false;
是不是這樣理解呢,
Thread.currentThread().interrupted(); 這個用于清除中斷狀態(tài),這樣下次調(diào)用Thread.interrupted()方法時就會一直返回為false,因為中斷標(biāo)志已經(jīng)被恢復(fù)了。
而調(diào)用isInterrupted 只是簡單的查詢中斷狀態(tài),不會對狀態(tài)進(jìn)行修改。
interrupt()是用來設(shè)置中斷狀態(tài)的。返回true說明中斷狀態(tài)被設(shè)置了而不是被清除了。我們調(diào)用sleep、wait等此類可中斷(throw InterruptedException)方法時,一旦方法拋出InterruptedException,當(dāng)前調(diào)用該方法的線程的中斷狀態(tài)就會被jvm自動清除了,就是說我們調(diào)用該線程的isInterrupted 方法時是返回false。如果你想保持中斷狀態(tài),可以再次調(diào)用interrupt方法設(shè)置中斷狀態(tài)。這樣做的原因是,java的中斷并不是真正的中斷線程,而只設(shè)置標(biāo)志位(中斷位)來通知用戶。如果你捕獲到中斷異常,說明當(dāng)前線程已經(jīng)被中斷,不需要繼續(xù)保持中斷位。
interrupted是靜態(tài)方法,返回的是當(dāng)前線程的中斷狀態(tài)。例如,如果當(dāng)前線程被中斷(沒有拋出中斷異常,否則中斷狀態(tài)就會被清除),你調(diào)用interrupted方法,第一次會返回true。然后,當(dāng)前線程的中斷狀態(tài)被方法內(nèi)部清除了。第二次調(diào)用時就會返回false。如果你剛開始一直調(diào)用isInterrupted,則會一直返回true,除非中間線程的中斷狀態(tài)被其他操作清除了。
ps:里面有interrupted()相關(guān)的詳細(xì)使用例子。
調(diào)用 Executor 的 shutdown() 方法會等待線程都執(zhí)行完畢之后再關(guān)閉,但是如果調(diào)用的是 shutdownNow() 方法,則相當(dāng)于調(diào)用每個線程的 interrupt() 方法。
如果只想中斷 Executor 中的一個線程,可以通過使用 submit() 方法來提交一個線程,它會返回一個 Future> 對象,通過調(diào)用該對象的 cancel(true) 方法就可以中斷線程。
Future> future = executorService.submit(() ->{//..
});
future.cancel(true);
threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和區(qū)別
最近在看并發(fā)編程,在使用到ThreadPoolExecutor時,對它的三個關(guān)閉方法(shutdown()、shutdownNow()、awaitTermination())產(chǎn)生了興趣,同時又感到迷惑。查了些資料,自己寫了測試代碼,總算有了個比較清晰的認(rèn)識。下面一起來看看這三個方法:
shutdown()
將線程池狀態(tài)置為SHUTDOWN,并不會立即停止:
停止接收外部submit的任務(wù)
內(nèi)部正在跑的任務(wù)和隊列里等待的任務(wù),會執(zhí)行完
等到第二步完成后,才真正停止
shutdownNow()
將線程池狀態(tài)置為STOP。企圖立即停止,事實上不一定:
跟shutdown()一樣,
先停止接收外部提交的任務(wù)
忽略隊列里等待的任務(wù)
嘗試將正在跑的任務(wù)interrupt中斷
返回未執(zhí)行的任務(wù)列表
它試圖終止線程的方法是通過調(diào)用Thread.interrupt()方法來實現(xiàn)的,但是大家知道,這種方法的作用有限,如果線程中沒有sleep 、wait、Condition、定時鎖等應(yīng)用, interrupt()方法是無法中斷當(dāng)前的線程的。所以,ShutdownNow()并不代表線程池就一定立即就能退出,它也可能必須要等待所有正在執(zhí)行的任務(wù)都執(zhí)行完成了才能退出。
但是大多數(shù)時候是能立即退出的
awaitTermination(long timeOut, TimeUnit unit)
當(dāng)前線程阻塞,直到
等所有已提交的任務(wù)(包括正在跑的和隊列中等待的)執(zhí)行完
或者等超時時間到
或者線程被中斷,拋出InterruptedException
然后返回true(shutdown請求后所有任務(wù)執(zhí)行完畢)或false(已超時)
實驗發(fā)現(xiàn),shuntdown()和awaitTermination()效果差不多,方法執(zhí)行之后,都要等到提交的任務(wù)全部執(zhí)行完才停。
shutdown()和shutdownNow()的區(qū)別
從字面意思就能理解,shutdownNow()能立即停止線程池,正在跑的和正在等待的任務(wù)都停下了。這樣做立即生效,但是風(fēng)險也比較大;
shutdown()只是關(guān)閉了提交通道,用submit()是無效的;而內(nèi)部該怎么跑還是怎么跑,跑完再停。
Between client threads and thread pool there is a queue of tasks. When your application shuts down, you must take care of two things: what is happening with queued tasks and how already running tasks are behaving (more on that later). Surprisingly many developers are not shutting down thread pool properly or consciously. There are two techniques: either let all queued tasks to execute (shutdown()) or drop them (shutdownNow()) - it totally depends on your use case.
shutdown()和awaitTermination()的區(qū)別
shutdown()后,不能再提交新的任務(wù)進(jìn)去;但是awaitTermination()后,可以繼續(xù)提交。
awaitTermination()是阻塞的,返回結(jié)果是線程池是否已停止(true/false);shutdown()不阻塞。
總結(jié)
優(yōu)雅的關(guān)閉,用shutdown()
想立馬關(guān)閉,并得到未執(zhí)行任務(wù)列表,用shutdownNow()
優(yōu)雅的關(guān)閉,并允許關(guān)閉聲明后新任務(wù)能提交,用awaitTermination()
關(guān)閉功能 【從強(qiáng)到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()
線程池的狀態(tài)
線程池的5種狀態(tài):Running、ShutDown、Stop、Tidying、Terminated。
線程池各個狀態(tài)切換框架圖:
總結(jié)
以上是生活随笔為你收集整理的线程的退出 java_(转)Java结束线程的三种方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用博奥如何导入单项工程电子表_用博奥如何
- 下一篇: java ifpresent_java映