優(yōu)雅的中斷線程,是一門藝術(shù)
總所周知耳幢,Thread.stop, Thread.suspend, Thread.resume 都已經(jīng)被廢棄了。因?yàn)樗鼈兲┝α伺菲。遣话踩木υ澹@種暴力中斷線程是一種不安全的操作,舉個(gè)栗子來(lái)說(shuō)明其可能造成的問(wèn)題:
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒邢隧,確保線程進(jìn)入運(yùn)行
Thread.sleep(1000);
// 暫停線程
thread.stop();
// thread.interrupt();
// 確保線程已經(jīng)銷毀
while (thread.isAlive()) { }
// 輸出結(jié)果
thread.print();
}
private static class StopThread extends Thread {
private int x = 0; private int y = 0;
@Override
public void run() {
// 這是一個(gè)同步原子操作
synchronized (this) {
++x;
try {
// 休眠3秒,模擬耗時(shí)操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++y;
}
}
public void print() {
System.out.println("x=" + x + " y=" + y);
}
}
}
上述代碼中店印,run方法里是一個(gè)同步的原子操作,x和y必須要共同增加府框,然而這里如果調(diào)用thread.stop()方法強(qiáng)制中斷線程吱窝,輸出如下:
x=1 y=0
沒(méi)有異常,也破壞了我們的預(yù)期迫靖。如果這種問(wèn)題出現(xiàn)在我們的程序中院峡,會(huì)引發(fā)難以預(yù)期的異常。因此這種不安全的方式很早就被廢棄了系宜。取而代之的是interrupt()照激,上述代碼如果采用thread.interrupt()方法,輸出結(jié)果如下:
x=1 y=1
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at ThreadTest$StopThread.run(ThreadTest.java:28)
x=1,y=1 這個(gè)結(jié)果是符合我們的預(yù)期盹牧,同時(shí)還拋出了個(gè)異常俩垃,這個(gè)異常下文詳說(shuō)。
interrupt() 它基于「一個(gè)線程不應(yīng)該由其他線程來(lái)強(qiáng)制中斷或停止汰寓,而是應(yīng)該由線程自己自行停止口柳。」思想有滑,是一個(gè)比較溫柔的做法跃闹,它更類似一個(gè)標(biāo)志位。其實(shí)作用不是中斷線程毛好,而是「通知線程應(yīng)該中斷了」望艺,具體到底中斷還是繼續(xù)運(yùn)行,應(yīng)該由被通知的線程自己處理肌访。
interrupt() 并不能真正的中斷線程找默,這點(diǎn)要謹(jǐn)記。需要被調(diào)用的線程自己進(jìn)行配合才行吼驶。也就是說(shuō)惩激,一個(gè)線程如果有被中斷的需求店煞,那么就需要這樣做:
- 在正常運(yùn)行任務(wù)時(shí),經(jīng)常檢查本線程的中斷標(biāo)志位咧欣,如果被設(shè)置了中斷標(biāo)志就自行停止線程浅缸。
- 在調(diào)用阻塞方法時(shí)正確處理InterruptedException異常。(例如:catch異常后就結(jié)束線程魄咕。)
先看下 Thread 類 interrupt 相關(guān)的幾個(gè)方法:
// 核心 interrupt 方法
public void interrupt() {
if (this != Thread.currentThread()) // 非本線程衩椒,需要檢查權(quán)限
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 僅僅設(shè)置interrupt標(biāo)志位
b.interrupt(this); // 調(diào)用如 I/O 操作定義的中斷方法
return;
}
}
interrupt0();
}
// 靜態(tài)方法,這個(gè)方法有點(diǎn)坑哮兰,調(diào)用該方法調(diào)用后會(huì)清除中斷狀態(tài)毛萌。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 這個(gè)方法不會(huì)清除中斷狀態(tài)
public boolean isInterrupted() {
return isInterrupted(false);
}
// 上面兩個(gè)方法會(huì)調(diào)用這個(gè)本地方法,參數(shù)代表是否清除中斷狀態(tài)
private native boolean isInterrupted(boolean ClearInterrupted);
首先講 interrupt() 方法:
- interrupt 中斷操作時(shí)喝滞,非自身打斷需要先檢測(cè)是否有中斷權(quán)限阁将,這由jvm的安全機(jī)制配置;
- 如果線程處于sleep, wait, join 等狀態(tài)右遭,那么線程將立即退出被阻塞狀態(tài)做盅,并拋出一個(gè)InterruptedException異常;
- 如果線程處于I/O阻塞狀態(tài)窘哈,將會(huì)拋出ClosedByInterruptException(IOException的子類)異常吹榴;
- 如果線程在Selector上被阻塞,select方法將立即返回滚婉;
- 如果非以上情況图筹,將直接標(biāo)記 interrupt 狀態(tài);
注意:interrupt 操作不會(huì)打斷所有阻塞让腹,只有上述阻塞情況才在jvm的打斷范圍內(nèi)远剩,如處于鎖阻塞的線程,不會(huì)受 interrupt 中斷骇窍;
阻塞情況下中斷瓜晤,拋出異常后線程恢復(fù)非中斷狀態(tài),即 interrupted = false
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task("1"));
t.start();
t.interrupt();
}
static class Task implements Runnable{
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread has been interrupt!");
}
System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());
System.out.println("task " + name + " is over");
}
}
}
輸出:
thread has been interrupt!
isInterrupted: false
task 1 is over
調(diào)用Thread.interrupted() 方法后線程恢復(fù)非中斷狀態(tài)
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task("1"));
t.start();
t.interrupt();
}
static class Task implements Runnable{
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("first :" + Thread.interrupted());
System.out.println("second:" + Thread.interrupted());
System.out.println("task " + name + " is over");
}
}
}
輸出結(jié)果:
first :true
second:false
task 1 is over
上述兩種隱含的狀態(tài)恢復(fù)操作腹纳,是符合常理的活鹰,因?yàn)榫€程標(biāo)記為中斷后,用戶沒(méi)有真正中斷線程只估,必然將其恢復(fù)為false。理論上Thread.interrupted()調(diào)用后着绷,如果已中斷蛔钙,應(yīng)該執(zhí)行退出操作,不會(huì)重復(fù)調(diào)用荠医。
多線程系列目錄(不斷更新中):
線程啟動(dòng)原理
線程中斷機(jī)制
多線程實(shí)現(xiàn)方式
FutureTask實(shí)現(xiàn)原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態(tài)控制
線程池之ThreadPoolExecutor執(zhí)行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優(yōu)雅關(guān)閉實(shí)踐