廢棄的API
提到中斷录淡,就會想起 stop 這個方法
但是,自己看圖 不說了
官網(wǎng)的解釋
線程中斷API
public static boolean interrupted
就是返回對應線程的中斷標志位是否為true返回當前線程的中斷標志位是否為true姻氨,但它還有一個重要的副作用,就是清空中斷標志位,也就是說,連續(xù)兩次調(diào)用interrupted()享扔,第一次返回的結(jié)果為true底桂,第二次一般就是false (除非同時又發(fā)生了一次中斷)。
public boolean isInterrupted()
就是返回對應線程的中斷標志位是否為true
public void interrupt()
表示中斷對應的線程
線程對中斷的反應
- RUNNABLE:線程在運行或具備運行條件只是在等待操作系統(tǒng)調(diào)度
- WAITING/TIMED_WAITING:線程在等待某個條件或超時
- BLOCKED:線程在等待鎖惧眠,試圖進入同步塊
- NEW/TERMINATED:線程還未啟動或已結(jié)束
RUNNABLE狀態(tài)
如果線程在運行中籽懦,interrupt()只是會設置線程的中斷標志位,沒有任何其它作用氛魁。線程應該在運行過程中合適的位置檢查中斷標志位暮顺,比如說,如果主體代碼是一個循環(huán)呆盖,可以在循環(huán)開始處進行檢查拖云,如下所示:
public class InterruptRunnableDemo extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// ... 單次循環(huán)代碼
}
System.out.println("done ");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new InterruptRunnableDemo();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
WAITING/TIMED_WAITING
線程執(zhí)行如下方法會進入WAITING狀態(tài):
public final void join() throws InterruptedException
public final void wait() throws InterruptedException
執(zhí)行如下方法會進入TIMED_WAITING狀態(tài):
public final native void wait(long timeout) throws InterruptedException;
public static native void sleep(long millis) throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException
在這些狀態(tài)時,對線程對象調(diào)用interrupt()會使得該線程拋出InterruptedException应又,需要注意的是宙项,拋出異常后,中斷標志位會被清空(線程的中斷標志位會由true重置為false株扛,因為線程為了處理異常已經(jīng)重新處于就緒狀態(tài)尤筐。),而不是被設置洞就。比如說盆繁,執(zhí)行如下代碼:
Thread t = new Thread (){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//exception被捕獲,但是為輸出為false 因為標志位會被清空
System.out.println(isInterrupted());
}
}
};
t.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
t.interrupt();//置為true
InterruptedException是一個受檢異常旬蟋,線程必須進行處理油昂。我們在異常處理中介紹過,處理異常的基本思路是倾贰,如果你知道怎么處理冕碟,就進行處理,如果不知道匆浙,就應該向上傳遞安寺,通常情況下,你不應該做的是首尼,捕獲異常然后忽略挑庶。
捕獲到InterruptedException,通常表示希望結(jié)束該線程软能,線程大概有兩種處理方式:
- 向上傳遞該異常迎捺,這使得該方法也變成了一個可中斷的方法,需要調(diào)用者進行處理
- 有些情況查排,不能向上傳遞異常凳枝,比如Thread的run方法,它的聲明是固定的雹嗦,不能拋出任何受檢異常范舀,這時,應該捕獲異常了罪,進行合適的清理操作锭环,清理后,一般應該調(diào)用Thread的interrupt方法設置中斷標志位泊藕,使得其他代碼有辦法知道它發(fā)生了中斷
第一種方式的示例代碼如下:
//拋出中斷異常辅辩,由調(diào)用者捕獲
public void interruptibleMethod() throws InterruptedException{
// ... 包含wait, join 或 sleep 方法
Thread.sleep(1000);
}
第二種方式的示例代碼如下:
public class InterruptWaitingDemo extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 模擬任務代碼
Thread.sleep(2000);
} catch (InterruptedException e) {
// ... 清理操作
System.out.println(isInterrupted());//false
// 重設中斷標志位為true
Thread.currentThread().interrupt();
}
}
System.out.println(isInterrupted());//true
}
public static void main(String[] args) {
InterruptWaitingDemo thread = new InterruptWaitingDemo();
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
thread.interrupt();
}
}
BLOCKED
如果線程在等待鎖,對線程對象調(diào)用interrupt()只是會設置線程的中斷標志位娃圆,線程依然會處于BLOCKED狀態(tài)玫锋,也就是說,interrupt()并不能使一個在等待鎖的線程真正”中斷”讼呢。我們看段代碼:
public class InterruptWaitingDemo extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 模擬任務代碼
Thread.sleep(2000);
} catch (InterruptedException e) {
// ... 清理操作
// 重設中斷標志位
Thread.currentThread().interrupt();
}
}
System.out.println(isInterrupted());
}
public static void main(String[] args) {
InterruptWaitingDemo thread = new InterruptWaitingDemo();
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
thread.interrupt();
}
}
test方法在持有鎖lock的情況下啟動線程a撩鹿,而線程a也去嘗試獲得鎖lock,所以會進入鎖等待隊列悦屏,隨后test調(diào)用線程a的interrupt方法并等待線程線程a結(jié)束节沦,線程a會結(jié)束嗎?不會础爬,interrupt方法只會設置線程的中斷標志甫贯,而并不會使它從鎖等待隊列中出來。
我們稍微修改下代碼看蚜,去掉test方法中的最后一行a.join叫搁,即變?yōu)椋?/p>
public static void test() throws InterruptedException {
synchronized (lock) {
A a = new A();
a.start();
Thread.sleep(1000);
a.interrupt();
}
//lock鎖釋放后 A線程重隊列中出來
}
這時,程序就會退出供炎。為什么呢戚扳?因為主線程不再等待線程a結(jié)束,釋放鎖lock后谓苟,線程a會獲得鎖彬祖,然后檢測到發(fā)生了中斷,所以會退出纽竣。
在使用synchronized關鍵字獲取鎖的過程中不響應中斷請求墓贿,這是synchronized的局限性。如果這對程序是一個問題蜓氨,應該使用顯式鎖聋袋,java中的Lock接口,它支持以響應中斷的方式獲取鎖穴吹。對于Lock.lock()幽勒,可以改用Lock.lockInterruptibly(),可被中斷的加鎖操作港令,它可以拋出中斷異常啥容。等同于等待時間無限長的Lock.tryLock(long time, TimeUnit unit)锈颗。
NEW/TERMINATE
如果線程尚未啟動(NEW),或者已經(jīng)結(jié)束(TERMINATED)咪惠,則調(diào)用interrupt()對它沒有任何效果击吱,中斷標志位也不會被設置。比如說遥昧,以下代碼的輸出都是false覆醇。
public class InterruptNotAliveDemo {
private static class A extends Thread {
@Override
public void run() {
}
}
public static void test() throws InterruptedException {
A a = new A();
a.interrupt();
System.out.println(a.isInterrupted());
a.start();
Thread.sleep(100);
a.interrupt();
System.out.println(a.isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
test();
}
}
IO操作
如果線程在等待IO操作,尤其是網(wǎng)絡IO炭臭,則會有一些特殊的處理永脓,我們沒有介紹過網(wǎng)絡,這里只是簡單介紹下鞋仍。
實現(xiàn)此InterruptibleChannel接口的通道是可中斷的:如果某個線程在可中斷通道上因調(diào)用某個阻塞的 I/O 操作(常見的操作一般有這些:serverSocketChannel. accept()常摧、socketChannel.connect、socketChannel.open威创、socketChannel.read排宰、socketChannel.write、fileChannel.read那婉、fileChannel.write)而進入阻塞狀態(tài)板甘,而另一個線程又調(diào)用了該阻塞線程的 interrupt 方法,這將導致該通道被關閉详炬,并且已阻塞線程接將會收到ClosedByInterruptException盐类,并且設置已阻塞線程的中斷狀態(tài)。另外呛谜,如果已設置某個線程的中斷狀態(tài)并且它在通道上調(diào)用某個阻塞的 I/O 操作在跳,則該通道將關閉并且該線程立即接收到 ClosedByInterruptException;并仍然設置其中斷狀態(tài)
如果線程阻塞于Selector調(diào)用隐岛,則線程的中斷標志位會被設置猫妙,同時,阻塞的調(diào)用會立即返回
我們重點介紹另一種情況聚凹,InputStream的read調(diào)用割坠,該操作是不可中斷的,如果流中沒有數(shù)據(jù)妒牙,read會阻塞 (但線程狀態(tài)依然是RUNNABLE)彼哼,且不響應interrupt(),與synchronized類似湘今,調(diào)用interrupt()只會設置線程的中斷標志敢朱,而不會真正”中斷”它,我們看段代碼。
public class InterruptReadDemo {
private static class A extends Thread {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
try {
System.out.println(System.in.read())//wait input
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("exit");
}
}
public static void main(String[] args) throws InterruptedException {
A t = new A();
t.start();
Thread.sleep(100);
t.interrupt();
}
}
線程t啟動后調(diào)用System.in.read()從標準輸入讀入一個字符拴签,不要輸入任何字符孝常,我們會看到,調(diào)用interrupt()不會中斷read()蚓哩,線程會一直運行茫因。
不過,有一個辦法可以中斷read()調(diào)用杖剪,那就是調(diào)用流的close方法,我們將代碼改為:
public class InterruptReadDemo {
private static class A extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println(System.in.read());
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("exit");
}
public void cancel() {
try {
System.in.close();
} catch (IOException e) {
}
interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
A t = new A();
t.start();
Thread.sleep(100);
t.cancel();
}
}
我們給線程定義了一個cancel方法驰贷,在該方法中盛嘿,調(diào)用了流的close方法,同時調(diào)用了interrupt方法括袒,這次次兆,程序會輸出:
-1
exit
也就是說,調(diào)用close方法后锹锰,read方法會返回芥炭,返回值為-1,表示流結(jié)束恃慧。
如何正確地取消/關閉線程
以上园蝠,我們可以看出,interrupt方法不一定會真正”中斷”線程痢士,它只是一種協(xié)作機制彪薛,如果不明白線程在做什么,不應該貿(mào)然的調(diào)用線程的interrupt方法怠蹂,以為這樣就能取消線程善延。
對于以線程提供服務的程序模塊而言,它應該封裝取消/關閉操作城侧,提供單獨的取消/關閉方法給調(diào)用者易遣,類似于InterruptReadDemo中演示的cancel方法,外部調(diào)用者應該調(diào)用這些方法而不是直接調(diào)用interrupt嫌佑。
Java并發(fā)庫的一些代碼就提供了單獨的取消/關閉方法豆茫,比如說,F(xiàn)uture接口提供了如下方法以取消任務:boolean cancel(boolean mayInterruptIfRunning);
再比如屋摇,ExecutorService提供了如下兩個關閉方法:
void shutdown();
List<Runnable> shutdownNow();
- Future和ExecutorService的API文檔對這些方法都進行了詳細說明澜薄,這是我們應該學習的方式。