深入分析JAVA 多線程--interrupt()和線程終止方式
一、interrupt() 介紹
interrupt() 定義在 Thread 類中,作用是中斷本線程。
本線程中斷自己是被允許的;其它線程調(diào)用本線程的 interrupt() 方法時(shí),會通過 checkAccess() 檢查權(quán)限。這有可能拋出 SecurityException 異常。
如果本線程是處于阻塞狀態(tài):調(diào)用線程的 wait() , wait(long) 或 wait(long, int) 會讓它進(jìn)入等待(阻塞)狀態(tài),或者調(diào)用線程的 join(),join(long),join(long, int),sleep(long),sleep(long, int) 也會讓它進(jìn)入阻塞狀態(tài)。若線程在阻塞狀態(tài)時(shí),調(diào)用了它的 interrupt() 方法,那么它的“中斷狀態(tài)”會被清除并且會收到一個 InterruptedException 異常。例如,線程通過 wait() 進(jìn)入阻塞狀態(tài),此時(shí)通過 interrupt() 中斷該線程;調(diào)用 interrupt() 會立即將線程的中斷標(biāo)記設(shè)為 true,但是由于線程處于阻塞狀態(tài),所以該“中斷標(biāo)記”會立即被清除為 “false”,同時(shí),會產(chǎn)生一個 InterruptedException 的異常。
如果線程被阻塞在一個 Selector 選擇器中,那么通過 interrupt() 中斷它時(shí);線程的中斷標(biāo)記會被設(shè)置為 true,并且它會立即從選擇操作中返回。
如果不屬于前面所說的情況,那么通過 interrupt() 中斷線程時(shí),它的中斷標(biāo)記會被設(shè)置為 true。
中斷一個“已終止的線程”不會產(chǎn)生任何操作。
二、線程終止方式
Thread中的 stop() 和 suspend() 方法,由于固有的不安全性,已經(jīng)建議不再使用!
下面,我先分別討論線程在“阻塞狀態(tài)”和“運(yùn)行狀態(tài)”的終止方式,然后再總結(jié)出一個通用的方式。
(一)、終止處于“阻塞狀態(tài)”的線程.
通常,我們通過“中斷”方式終止處于“阻塞狀態(tài)”的線程。
當(dāng)線程由于被調(diào)用了 sleep(),,wait(),join() 等方法而進(jìn)入阻塞狀態(tài);若此時(shí)調(diào)用線程的 interrupt() 將線程的中斷標(biāo)記設(shè)為 true。由于處于阻塞狀態(tài),中斷標(biāo)記會被清除,同時(shí)產(chǎn)生一個InterruptedException 異常。將 InterruptedException 放在適當(dāng)?shù)奈恢镁湍芙K止線程,形式如下:
public void run() { try { while (true) { // 執(zhí)行業(yè)務(wù) } } catch (InterruptedException ie) { // 由于產(chǎn)生InterruptedException異常,退出while(true)循環(huán),線程終止! } }
說明:
在while(true)中不斷的執(zhí)行業(yè)務(wù)代碼,當(dāng)線程處于阻塞狀態(tài)時(shí),調(diào)用線程的 interrupt() 產(chǎn)生 InterruptedException 中斷。中斷的捕獲在 while(true) 之外,這樣就退出了 while(true) 循環(huán)!
注意:
對 InterruptedException 的捕獲務(wù)一般放在 while(true) 循環(huán)體的外面,這樣,在產(chǎn)生異常時(shí)就退出了 while(true) 循環(huán)。否則,InterruptedException 在 while(true) 循環(huán)體之內(nèi),就需要額外的添加退出處理。形式如下:
public void run() { while (true) { try { // 執(zhí)行任務(wù)... } catch (InterruptedException ie) { // InterruptedException在while(true)循環(huán)體內(nèi)。 // 當(dāng)線程產(chǎn)生了InterruptedException異常時(shí),while(true)仍能繼續(xù)運(yùn)行!需要手動退出 break; } } }
說明:
上面的 InterruptedException 異常的捕獲在 whle(true) 之內(nèi)。當(dāng)產(chǎn)生 InterruptedException 異常時(shí),被 catch 處理之外,仍然在 while(true) 循環(huán)體內(nèi);要退出 while(true) 循環(huán)體,需要額外的執(zhí)行退出while(true) 的操作。
(二)、終止處于“運(yùn)行狀態(tài)”的線程
通常,我們通過“標(biāo)記”方式終止處于“運(yùn)行狀態(tài)”的線程。其中,包括“中斷標(biāo)記”和“額外添加標(biāo)記”。
1、通過“中斷標(biāo)記”終止線程
public void run() { while (!isInterrupted()) { // 執(zhí)行任務(wù)... } }
說明:
isInterrupted() 是判斷線程的中斷標(biāo)記是不是為 true。當(dāng)線程處于運(yùn)行狀態(tài),并且我們需要終止它時(shí);可以調(diào)用線程的 interrupt() 方法,使用線程的中斷標(biāo)記為 true,即 isInterrupted() 會返回true。此時(shí),就會退出while循環(huán)。
注意:interrupt() 并不會終止處于“運(yùn)行狀態(tài)”的線程!它會將線程的中斷標(biāo)記設(shè)為 true。
2、通過“額外添加標(biāo)記”終止線程
private volatile boolean flag= true; protected void stopTask() { flag = false; } public void run() { while (flag) { // 執(zhí)行任務(wù)... } }
說明:
線程中有一個 flag 標(biāo)記,它的默認(rèn)值是 true;并且我們提供 stopTask() 來設(shè)置 flag 標(biāo)記。當(dāng)我們需要終止該線程時(shí),調(diào)用該線程的 stopTask() 方法就可以讓線程退出 while 循環(huán)。
注意:將 flag 定義為 volatile 類型,是為了保證 flag 的可見性。即其它線程通過 stopTask() 修改了 flag 之后,本線程能看到修改后的 flag 的值。
(三)、通過方式
綜合線程處于“阻塞狀態(tài)”和“運(yùn)行狀態(tài)”的終止方式,比較通用的終止線程的形式如下:
public void run() { try { // 1. isInterrupted()保證,只要中斷標(biāo)記為true就終止線程。 while (!isInterrupted()) { // 執(zhí)行任務(wù)... } } catch (InterruptedException ie) { // 2. InterruptedException異常保證,當(dāng)InterruptedException異常產(chǎn)生時(shí),線程被終止。 } }
1、isInterrupted()保證,只要中斷標(biāo)記為 true 就終止線程。
2、InterruptedException 異常保證,當(dāng) InterruptedException 異常產(chǎn)生時(shí),線程被終止。
三、示例
public class InterruptTest { public static void main(String[] args) { try { Thread t1 = new MyThread("t1"); // 新建線程t1 System.out.println(t1.getName() + "[" + t1.getState() + "] is new."); t1.start();// 啟動線程t1 System.out.println(t1.getName() + "[" + t1.getState() + "] is started."); Thread.sleep(300);// 休眠300毫秒,然后主線程給t1發(fā)“中斷”指令,查看t1狀態(tài) t1.interrupt(); System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted."); Thread.sleep(300);// 休眠300毫秒,然后查看t1狀態(tài) System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted now."); }catch(InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread{ public MyThread(String name) { super(name); } @Override public void run() { try { int i = 0; while(!isInterrupted()) { Thread.sleep(100);// 休眠100毫秒 ++i; System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] loop " + i); } }catch(InterruptedException e) { System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] catch InterruptedException"); } } }
運(yùn)行結(jié)果
t1 [ NEW ] is new.
t1 [ RUNNABLE ] is started.
t1 [ RUNNABLE ] loop 1
t1 [ RUNNABLE ] loop 2
t1 [ RUNNABLE ] loop 3
t1 [ RUNNABLE ] catch InterruptedException
t1 [ TERMINATED ] is interrupted.
t1 [ TERMINATED ] is interrupted now.
說明:
①、主線程 main 中通過 new MyThread("t1") 創(chuàng)建線程 t1,之后通過 t1.start() 啟動線程 t1。
②、t1 啟動之后,會不斷的檢查它的中斷標(biāo)記,如果中斷標(biāo)記為“false”;則休眠 100ms。
③、t1 休眠之后,會切換到主線程main;主線程再次運(yùn)行時(shí),會執(zhí)行t1.interrupt()中斷線程t1。t1收到中斷指令之后,會將t1的中斷標(biāo)記設(shè)置“false”,而且會拋出 InterruptedException 異常。在 t1 的 run() 方法中,是在循環(huán)體 while 之外捕獲的異常;因此循環(huán)被終止。
我們對上面的結(jié)果進(jìn)行小小的修改,將run()方法中捕獲InterruptedException異常的代碼塊移到while循環(huán)體內(nèi)。
public class InterruptTest { public static void main(String[] args) { try { Thread t1 = new MyThread("t1"); // 新建線程t1 System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new."); t1.start();// 啟動線程t1 System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started."); Thread.sleep(300);// 休眠300毫秒,然后主線程給t1發(fā)“中斷”指令,查看t1狀態(tài) t1.interrupt(); System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted."); Thread.sleep(300);// 休眠300毫秒,然后查看t1狀態(tài) System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now."); }catch(InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread{ public MyThread(String name) { super(name); } @Override public void run() { int i = 0; while(!isInterrupted()) { try { Thread.sleep(100); // 休眠100ms } catch (InterruptedException ie) { System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException."); } i++; System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i); } } }
運(yùn)行結(jié)果
t1 [ NEW ] is new.
t1 [ RUNNABLE ] is started.
t1 [ RUNNABLE ] loop 1
t1 [ RUNNABLE ] loop 2
t1 [ TIMED_WAITING ] is interrupted.
t1 [ RUNNABLE ] catch InterruptedException.
t1 [ RUNNABLE ] loop 3
t1 [ RUNNABLE ] loop 4
t1 [ RUNNABLE ] loop 5
t1 [ RUNNABLE ] loop 6
t1 [ RUNNABLE ] is interrupted now.
t1 [ RUNNABLE ] loop 7
...... // 無限循環(huán)
說明:
程序進(jìn)入了死循環(huán)了。
這是因?yàn)椋瑃1在“等待(阻塞)狀態(tài)”時(shí),被 interrupt() 中斷;此時(shí),會清除中斷標(biāo)記(即 isInterrupted() 會返回 false),而且會拋出 InterruptedException 異常(該異常在while循環(huán)體內(nèi)被捕獲)。因此,t1理所當(dāng)然的會進(jìn)入死循環(huán)了。
解決該問題,需要我們在捕獲異常時(shí),額外的進(jìn)行退出 while 循環(huán)的處理。例如,在 MyThread 的 catch(InterruptedException) 中添加 break 或 return 就能解決該問題。
下面是通過“額外添加標(biāo)記”的方式終止“狀態(tài)狀態(tài)”的線程的示例:
public class InterruptTest { public static void main(String[] args) { try { MyThread t1 = new MyThread("t1"); // 新建線程t1 System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new."); t1.start();// 啟動線程t1 System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started."); Thread.sleep(300);// 休眠300毫秒,然后主線程給t1發(fā)“中斷”指令,查看t1狀態(tài) t1.stopTask(); System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted."); Thread.sleep(300);// 休眠300毫秒,然后查看t1狀態(tài) System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now."); }catch(InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread{ private volatile boolean flag = true; public void stopTask() { flag = false; } public MyThread(String name) { super(name); } @Override public void run() { synchronized (this) { int i = 0; while(flag) { try { Thread.sleep(100); // 休眠100ms } catch (InterruptedException ie) { System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException."); break; } i++; System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i); } } } }
運(yùn)行結(jié)果
t1 [ NEW ] is new.
t1 [ RUNNABLE ] is started.
t1 [ RUNNABLE ] loop 1
t1 [ RUNNABLE ] loop 2
t1 [ RUNNABLE ] loop 3
t1 [ RUNNABLE ] is interrupted.
t1 [ TERMINATED ] is interrupted now.
四、interrupted() 和 isInterrupted()的區(qū)別
interrupted() 和 isInterrupted()都能夠用于檢測對象的“中斷標(biāo)記”。
區(qū)別是,interrupted() 除了返回中斷標(biāo)記之外,它還會清除中斷標(biāo)記(即將中斷標(biāo)記設(shè)為 false);而 isInterrupted() 僅僅返回中斷標(biāo)記。
以上就是深入分析JAVA 多線程--interrupt()和線程終止方式的詳細(xì)內(nèi)容,更多關(guān)于JAVA 多線程--interrupt()和線程終止的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring緩存注解@Cacheable @CacheEvit @CachePut使用介紹
Spring在3.1版本,就提供了一條基于注解的緩存策略,實(shí)際使用起來還是很絲滑的,本文將針對幾個常用的注解進(jìn)行簡單的介紹說明,有需要的小伙伴可以嘗試一下2021-07-07SpringBoot使用AOP實(shí)現(xiàn)統(tǒng)計(jì)全局接口訪問次數(shù)詳解
這篇文章主要介紹了SpringBoot通過AOP實(shí)現(xiàn)對全局接口訪問次數(shù)的統(tǒng)計(jì),文章從相關(guān)問題展開全文內(nèi)容詳情,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06解決SpringBoot webSocket 資源無法加載、tomcat啟動報(bào)錯的問題
這篇文章主要介紹了解決SpringBoot webSocket 資源無法加載、tomcat啟動報(bào)錯的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11