JAVA多線程之中斷機(jī)制及處理中斷的方法
一,介紹
這篇文章主要記錄使用 interrupt() 方法中斷線程,以及如何對(duì)InterruptedException進(jìn)行處理。感覺對(duì)InterruptedException異常進(jìn)行處理是一件謹(jǐn)慎且有技巧的活兒。
由于使用stop()方法停止線程非常的暴力,人家線程運(yùn)行的好好的,突然就把人家殺死了,線程占用的鎖被強(qiáng)制釋放,極易導(dǎo)致數(shù)據(jù)的不一致性??蓞⒖歼@篇文章對(duì)stop()方法的介紹。
因此,提出了一種溫和的方式:請(qǐng)求另外一個(gè)線程不要再執(zhí)行了,這就是中斷方式。
二,中斷及如何響應(yīng)中斷?
如何優(yōu)雅地響應(yīng)中斷真的是太高深了,看到這篇文章:Java 理論與實(shí)踐: 處理 InterruptedException就嚇了一跳。下面只是記錄一些最簡單的我對(duì)響應(yīng)中斷的理解。
假設(shè)某個(gè)線程要不停地處理某件事情(比如 i 一直自增),但是還有個(gè)要求:在處理事情前,先要檢查下這個(gè)線程是否被中斷,如果已經(jīng)被中斷,處理就應(yīng)該結(jié)束。
下面是一些例子,這些例子摘自書本:
public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(20);//modify 2000 to 20 thread.interrupt();//請(qǐng)求中斷MyThread線程 } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } }
main線程睡眠20ms后,執(zhí)行第8行中斷MyThread線程。
在《java并發(fā)編程實(shí)戰(zhàn)》中有一句話:“對(duì)中斷操作的正確理解是:它并不會(huì)真正地中斷一個(gè)線程,而只是發(fā)出中斷請(qǐng)求,然后由線程在下一個(gè)合適的時(shí)刻中斷自己”。以上面的示例對(duì)這句話作一個(gè)解讀,如下:
1、main線程向 MyThread線程發(fā)出了中斷請(qǐng)求
2、假如 MyThread線程此刻正在執(zhí)行第10行,打印。對(duì)于 MyThread線程來說,此時(shí)并不是一個(gè)“合適”的時(shí)刻。因?yàn)椋杭热粓?zhí)行到了 println語句的地方,那就應(yīng)該把 i的值打印出來,這樣才是一種“一致的狀態(tài)”。關(guān)于一致狀態(tài)的理解,可參考書中的一段話:(在 java中沒有一種安全的搶占式方法來停止線程,而是一種“協(xié)作”方式,它能夠使用“共享的數(shù)據(jù)結(jié)構(gòu)”盡可能地處于一致的狀態(tài))
3、既然第10行不是一個(gè)合適的時(shí)刻,那么:“下一個(gè)合適的時(shí)刻”到底在哪里?答案就是:第6行的 if語句。因?yàn)樵诘?行,MyThread線程檢測(cè)到了 main線程的中斷請(qǐng)求,于是:break for循環(huán),滿足程序的“執(zhí)行語義”。在這里,執(zhí)行語義是:遍歷一次 for循環(huán),就打印出 i
public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("should be stopped and exit"); break; } System.out.println("i=" + (i + 1)); } System.out.println("this line is also executed. thread does not stopped");//盡管線程被中斷,但并沒有結(jié)束運(yùn)行。這行代碼還是會(huì)被執(zhí)行 } }
當(dāng)MyThread獲得CPU執(zhí)行時(shí),第6行的 if 測(cè)試中,檢測(cè)到中斷標(biāo)識(shí)被設(shè)置。即MyThread線程檢測(cè)到了main線程想要中斷它的 請(qǐng)求。
大多數(shù)情況下,MyThread檢測(cè)到了中斷請(qǐng)求,對(duì)該中斷的響應(yīng)是:退出執(zhí)行(或者說是結(jié)束執(zhí)行)。
但是,上面第5至8行for循環(huán),是執(zhí)行break語句跳出for循環(huán)。但是,線程并沒有結(jié)束,它只是跳出了for循環(huán)而已,它還會(huì)繼續(xù)執(zhí)行第12行的代碼....
因此,我們的問題是,當(dāng)收到了中斷請(qǐng)求后,如何結(jié)束該線程呢?
一種可行的方法是使用 return 語句 而不是 break語句。。。。。哈哈。。。
當(dāng)然,一種更優(yōu)雅的方式則是:拋出InterruptedException異常。
看下面MyThread類的代碼:
public class MyThread extends Thread { @Override public void run() { super.run(); try{ for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("should be stopped and exit"); throw new InterruptedException(); } System.out.println("i=" + (i + 1)); } System.out.println("this line cannot be executed. cause thread throws exception");//這行語句不會(huì)被執(zhí)行!!! }catch(InterruptedException e){ System.out.println("catch interrupted exception"); e.printStackTrace(); } } }
當(dāng)MyThread線程檢測(cè)到中斷標(biāo)識(shí)為true后,在第9行拋出InterruptedException異常。這樣,該線程就不能再執(zhí)行其他的正常語句了(如,第13行語句不會(huì)執(zhí)行)。這里表明:interrupt()方法有兩個(gè)作用,一個(gè)是將線程的中斷狀態(tài)置位(中斷狀態(tài)由false變成true);另一個(gè)則是:讓被中斷的線程拋出InterruptedException異常。
這是很重要的。這樣,對(duì)于那些阻塞方法(比如 wait() 和 sleep())而言,當(dāng)另一個(gè)線程調(diào)用interrupt()中斷該線程時(shí),該線程會(huì)從阻塞狀態(tài)退出并且拋出中斷異常。這樣,我們就可以捕捉到中斷異常,并根據(jù)實(shí)際情況對(duì)該線程從阻塞方法中異常退出而進(jìn)行一些處理。
比如說:線程A獲得了鎖進(jìn)入了同步代碼塊中,但由于條件不足調(diào)用 wait() 方法阻塞了。這個(gè)時(shí)候,線程B執(zhí)行 threadA.interrupt()請(qǐng)求中斷線程A,此時(shí)線程A就會(huì)拋出InterruptedException,我們就可以在catch中捕獲到這個(gè)異常并進(jìn)行相應(yīng)處理(比如進(jìn)一步往上拋出)
因此,上面就是一個(gè)采用拋出異常的方式來結(jié)束線程的示例。盡管該示例的實(shí)用性不大。原因在 IBM的這篇博文中:我們 生吞了中斷。
在第14行,我們直接catch了異常,然后打印輸出了一下而已,調(diào)用棧中的更高層的代碼是無法獲得關(guān)于該異常的信息的。
第16行的e.printStackTrace()作用就相當(dāng)于
“(僅僅記錄 InterruptedException 也不是明智的做法,因?yàn)榈鹊饺藖碜x取日志的時(shí)候,再來對(duì)它作出處理就為時(shí)已晚了。)”---摘自參考博文
上面我們是在run()方法中拋出異常,符合這里描述的:
有時(shí)候拋出 InterruptedException 并不合適,例如當(dāng)由 Runnable 定義的任務(wù)調(diào)用一個(gè)
可中斷的方法時(shí),就是如此。在這種情況下,不能重新拋出 InterruptedException,但是
您也不想什么都不做。當(dāng)一個(gè)阻塞方法檢測(cè)到中斷并拋出 InterruptedException 時(shí),它
清除中斷狀態(tài)。如果捕捉到 InterruptedException 但是不能重新拋出它,那么應(yīng)該保留
中斷發(fā)生的證據(jù),以便調(diào)用棧中更高層的代碼能知道中斷,并對(duì)中斷作出響應(yīng)。該任務(wù)可以
通過調(diào)用 interrupt() 以 “重新中斷” 當(dāng)前線程來完成,如清單 3 所示。 -----“摘自參考博文”
因?yàn)?,run方法是實(shí)現(xiàn)的Runnable接口中的方法。不能像下面這樣定義,也即上面所說的:“不能重新拋出InterruptedException”。
@Override public void run() throws InterruptedException{//這是錯(cuò)誤的 //do something...
因此,一個(gè)更好的解決方案是:調(diào)用 interrupt()
以 “重新中斷” 當(dāng)前線程。改進(jìn)MyThread類中catch異常的方式,如下:
public class MyThread extends Thread { @Override public void run() { super.run(); try{ for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("should be stopped and exit"); throw new InterruptedException(); } System.out.println("i=" + (i + 1)); } System.out.println("this line cannot be executed. cause thread throws exception"); }catch(InterruptedException e){ /**這樣處理不好 * System.out.println("catch interrupted exception"); * e.printStackTrace(); */ Thread.currentThread().interrupt();//這樣處理比較好 } } }
這樣,就由 生吞異常 變成了 將 異常事件 進(jìn)一步擴(kuò)散了。
參考博文:Java 理論與實(shí)踐: 處理 InterruptedException
參考書籍:《Java多線程編程核心技術(shù)》
到此這篇關(guān)于JAVA多線程之中斷機(jī)制及處理中斷的方法的文章就介紹到這了,更多相關(guān)java多線程中斷機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java -D參數(shù)設(shè)置系統(tǒng)屬性無效問題及解決
這篇文章主要介紹了java -D參數(shù)設(shè)置系統(tǒng)屬性無效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12最新IDEA?2022基于JVM極致優(yōu)化?IDEA啟動(dòng)速度的方法
這篇文章主要介紹了IDEA?2022最新版?基于?JVM極致優(yōu)化?IDEA?啟動(dòng)速度,需要的朋友可以參考下2022-08-08Java實(shí)現(xiàn)的生成二維碼統(tǒng)計(jì)掃描次數(shù)并轉(zhuǎn)發(fā)到某個(gè)地址功能詳解
這篇文章主要介紹了Java實(shí)現(xiàn)的生成二維碼統(tǒng)計(jì)掃描次數(shù)并轉(zhuǎn)發(fā)到某個(gè)地址功能,可實(shí)現(xiàn)生成帶統(tǒng)計(jì)功能的二維碼,涉及java二維碼的生成、參數(shù)傳遞、解析等相關(guān)操作技巧,需要的朋友可以參考下2018-07-07IDEA運(yùn)行SSM項(xiàng)目的超詳細(xì)圖解教程
SSM項(xiàng)目部署其實(shí)很簡單,下面這篇文章主要給大家介紹了關(guān)于IDEA運(yùn)行SSM項(xiàng)目的超詳細(xì)圖解教程,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10