一文揭曉如何在Java中終止一個(gè)線程
簡(jiǎn)介
工作中我們經(jīng)常會(huì)用到線程,一般情況下我們讓線程執(zhí)行就完事了,那么你們有沒(méi)有想過(guò)如何去終止一個(gè)正在運(yùn)行的線程呢?
今天帶大家一起來(lái)看看。
Thread.stop被禁用之謎
問(wèn)道怎么終止一個(gè)線程,可能大多數(shù)人都知道可以調(diào)用Thread.stop方法。
但是這個(gè)方法從jdk1.2之后就不推薦使用了,為什么不推薦使用呢?
我們先來(lái)看下這個(gè)方法的定義:
@Deprecated(since="1.2") public final void stop() { @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); }
從代碼我們可以看出,stop這個(gè)方法首先檢測(cè)有沒(méi)有線程訪問(wèn)的權(quán)限。如果有權(quán)限的話,來(lái)判斷當(dāng)前的線程是否是剛剛創(chuàng)建的線程,如果不是剛剛創(chuàng)建的,那么就調(diào)用resume方法來(lái)解除線程的暫停狀態(tài)。
最后調(diào)用stop0方法來(lái)結(jié)束線程。
其中resume和stop0是兩個(gè)native的方法,具體的實(shí)現(xiàn)這里就不講了。
看起來(lái)stop方法很合理,沒(méi)有什么問(wèn)題。那么為什么說(shuō)這個(gè)方法是不安全的呢?
接下來(lái)我們來(lái)看一個(gè)例子。
我們創(chuàng)建一個(gè)NumberCounter的類,這個(gè)類有一個(gè)increaseNumber的安全方法,用來(lái)對(duì)number加一:
public class NumberCounter { //要保存的數(shù)字 private volatile int number=0; //數(shù)字計(jì)數(shù)器的邏輯是否完整 private volatile boolean flag = false; public synchronized int increaseNumber() throws InterruptedException { if(flag){ //邏輯不完整 throw new RuntimeException("邏輯不完整,數(shù)字計(jì)數(shù)器未執(zhí)行完畢"); } //開(kāi)始執(zhí)行邏輯 flag = true; //do something Thread.sleep(5000); number++; //執(zhí)行完畢 flag=false; return number; } }
事實(shí)上,在實(shí)際工作中這樣的方法可能需要執(zhí)行比較久的時(shí)間,所以這里我們通過(guò)調(diào)用Thread.sleep來(lái)模擬這個(gè)耗時(shí)操作。
這里我們還有一個(gè)flag參數(shù),來(lái)標(biāo)志這個(gè)increaseNumber方法是否成功執(zhí)行完畢。
好了,接下來(lái)我們?cè)谝粋€(gè)線程中調(diào)用這個(gè)類的方法,看看會(huì)發(fā)生什么:
public static void main(String[] args) throws InterruptedException { NumberCounter numberCounter= new NumberCounter(); Thread thread = new Thread(()->{ while (true){ try { numberCounter.increaseNumber(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(3000); thread.stop(); numberCounter.increaseNumber(); }
這里,我們創(chuàng)建了一個(gè)線程,等這個(gè)線程運(yùn)行3秒鐘之后,直接調(diào)用thread.stop方法,結(jié)果我們發(fā)現(xiàn)出現(xiàn)了下面的異常:
Exception in thread "main" java.lang.RuntimeException: 邏輯不完整,數(shù)字計(jì)數(shù)器未執(zhí)行完畢
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
at com.flydean.Main.main(Main.java:18)
這是因?yàn)閠hread.stop方法直接終止了線程的運(yùn)行,導(dǎo)致mberCounter.increaseNumber未執(zhí)行完畢。
但是這個(gè)未執(zhí)行完畢的狀態(tài)是隱藏的,如果使用thread.stop方法來(lái)終止線程,很有可能導(dǎo)致未知的結(jié)果。
所以,我們說(shuō)thread.stop是不安全的。
怎么才能安全
那么,如果不調(diào)用thread.stop方法,怎么才能安全的終止線程呢?
所謂安全,那就是需要讓線程里面的邏輯執(zhí)行完畢,而不是執(zhí)行一半。
為了實(shí)現(xiàn)這個(gè)效果,Thread為我們提供了三個(gè)比較類似的方法,他們分別是interrupt、interrupted和isInterrupted。
interrupt是給線程設(shè)置中斷標(biāo)志;interrupted是檢測(cè)中斷并清除中斷狀態(tài);isInterrupted只檢測(cè)中斷。還有重要的一點(diǎn)就是interrupted是類方法,作用于當(dāng)前線程,interrupt和isInterrupted作用于此線程,即代碼中調(diào)用此方法的實(shí)例所代表的線程。
interrupt就是中斷的方法,它的工作流程如下:
- 如果當(dāng)前線程實(shí)例在調(diào)用Object類的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)方法,或者在該實(shí)例中調(diào)用了Thread.sleep(long)或Thread.sleep(long,int)方法,并且正在阻塞狀態(tài)中時(shí),則其中斷狀態(tài)將被清除,并將收到InterruptedException。
- 如果此線程在InterruptibleChannel上的I/O操作中處于被阻塞狀態(tài),則該channel將被關(guān)閉,該線程的中斷狀態(tài)將被設(shè)置為true,并且該線程將收到j(luò)ava.nio.channels.ClosedByInterruptException異常。
- 如果此線程在java.nio.channels.Selector中處于被被阻塞狀態(tài),則將設(shè)置該線程的中斷狀態(tài)為true,并且它將立即從select操作中返回。
- 如果上面的情況都不成立,則設(shè)置中斷狀態(tài)為true。
在上面的例子中,NumberCounter的increaseNumber方法中,我們調(diào)用了Thread.sleep方法,所以如果在這個(gè)時(shí)候,調(diào)用了thread的interrupt方法,線程就會(huì)拋出一個(gè)InterruptedException異常。
我們把上面調(diào)用的例子改成下面這樣:
public static void main(String[] args) throws InterruptedException { NumberCounter numberCounter = new NumberCounter(); Thread thread = new Thread(() -> { while (true) { try { numberCounter.increaseNumber(); } catch (InterruptedException e) { System.out.println("捕獲InterruptedException"); throw new RuntimeException(e); } } }); thread.start(); Thread.sleep(500); thread.interrupt(); numberCounter.increaseNumber(); }
運(yùn)行之后再試一次:
Exception in thread "main" Exception in thread "Thread-0" java.lang.RuntimeException: 邏輯不完整,數(shù)字計(jì)數(shù)器未執(zhí)行完畢
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
at com.flydean.Main2.main(Main2.java:21)
java.lang.RuntimeException: java.lang.thread.interrupt: sleep interrupted
at com.flydean.Main2.lambda$main$0(Main2.java:13)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:17)
at com.flydean.Main2.lambda$main$0(Main2.java:10)
... 1 more
捕獲InterruptedException
可以看到,我們捕獲到了這個(gè)InterruptedException,并且得知具體的原因是sleep interrupted。
捕獲異常之后的處理
從上面的分析可以得知,thread.stop跟thread.interrupt的表現(xiàn)機(jī)制是不一樣的。thread.stop屬于悄悄終止,我們程序不知道,所以會(huì)導(dǎo)致數(shù)據(jù)不一致,從而產(chǎn)生一些未知的異常。
而thread.interrupt會(huì)顯示的拋出InterruptedException,當(dāng)我們捕捉到這個(gè)異常的時(shí)候,我們就知道線程里面的邏輯在執(zhí)行的過(guò)程中受到了外部作用的干擾,那么我們就可以執(zhí)行一些數(shù)據(jù)恢復(fù)或者數(shù)據(jù)校驗(yàn)的動(dòng)作。
在上面的代碼中,我們是捕獲到了這個(gè)異常,打印出異常日志,然后向上拋出一個(gè)RuntimeException。
正常情況下我們是需要在捕獲異常之后,進(jìn)行一些處理。
那么自己處理完這個(gè)異常之后,是不是就完美了呢?
答案是否定的。
因?yàn)槿绻覀冏约禾幚砹诉@個(gè)InterruptedException, 那么程序中其他部分如果有依賴這個(gè)InterruptedException的話,就可能會(huì)出現(xiàn)數(shù)據(jù)不一致的情況。
所以我們?cè)谧约禾幚硗闕nterruptedException之后,還需要再次拋出這個(gè)異常。
怎么拋出InterruptedException異常呢?
有兩種方式,第一種就是在調(diào)用Thread.interrupted()清除了中斷標(biāo)志之后立即拋出:
if (Thread.interrupted()) // Clears interrupted status! throw new InterruptedException();
還有一種方式就是,在捕獲異常之后,調(diào)用Thread.currentThread().interrupt()再次中斷線程。
public void run () { try { while (true) { // do stuff } }catch (InterruptedException e) { LOGGER.log(Level.WARN, "Interrupted!", e); // Restore interrupted state... Thread.currentThread().interrupt(); } }
這兩種方式都能達(dá)到預(yù)想的效果。
總結(jié)
線程不能調(diào)用stop來(lái)終止主要是因?yàn)椴粫?huì)拋出異常,從而導(dǎo)致一些安全和數(shù)據(jù)不一致的問(wèn)題。所以,最好的方式就是調(diào)用interrupt方法來(lái)處理。
到此這篇關(guān)于一文揭曉如何在Java中終止一個(gè)線程的文章就介紹到這了,更多相關(guān)Java終止線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Data?JPA映射自定義實(shí)體類操作
這篇文章主要介紹了Spring?Data?JPA映射自定義實(shí)體類操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11解析電子郵件的基本概念及JavaMail API郵件功能使用
這篇文章主要介紹了電子郵件的基本概念及JavaMail API郵件功能使用,包括用Java來(lái)發(fā)送郵件的示例,需要的朋友可以參考下2016-02-02dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter
這篇文章主要介紹了dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03基于Java編寫(xiě)一個(gè)粽子大作戰(zhàn)小游戲
端午節(jié),又稱龍舟節(jié)、重午節(jié),是中國(guó)的傳統(tǒng)節(jié)日之一,每年農(nóng)歷五月初五慶祝,雖然端午假期已經(jīng)過(guò)去了,小編還是用Java編寫(xiě)了一個(gè)粽子大作戰(zhàn)小游戲,感興趣的可以了解一下2023-06-06Spring里的Async注解實(shí)現(xiàn)異步操作的方法步驟
這篇文章主要介紹了Spring里的Async注解實(shí)現(xiàn)異步操作的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解
這篇文章主要介紹了Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹
這篇文章主要介紹了java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹的相關(guān)資料,對(duì)于初學(xué)者還是有必要了解下,細(xì)致說(shuō)明他們是什么,需要的朋友可以參考下2016-11-11SpringBoot集成Mybatis的實(shí)現(xiàn)步驟
這篇文章主要介紹了SpringBoot集成Mybatis的實(shí)現(xiàn)步驟,本文通過(guò)SpringBoot +MyBatis 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)學(xué)生表的查詢操作,需要的朋友可以參考下2020-12-12