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