【Java并發(fā)編程實戰(zhàn)】-----線程的中斷(interrupt)機制

廢棄的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文檔對這些方法都進行了詳細說明澜薄,這是我們應該學習的方式。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摊册,一起剝皮案震驚了整個濱河市肤京,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖忘分,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棋枕,死亡現(xiàn)場離奇詭異,居然都是意外死亡妒峦,警方通過查閱死者的電腦和手機重斑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肯骇,“玉大人窥浪,你說我怎么就攤上這事〉驯” “怎么了漾脂?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胚鸯。 經(jīng)常有香客問我骨稿,道長,這世上最難降的妖魔是什么姜钳? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任坦冠,我火速辦了婚禮,結(jié)果婚禮上哥桥,老公的妹妹穿的比我還像新娘辙浑。我一直安慰自己,他們只是感情好拟糕,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布例衍。 她就那樣靜靜地躺著,像睡著了一般已卸。 火紅的嫁衣襯著肌膚如雪佛玄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天累澡,我揣著相機與錄音梦抢,去河邊找鬼。 笑死愧哟,一個胖子當著我的面吹牛奥吩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蕊梧,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼霞赫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肥矢?” 一聲冷哼從身側(cè)響起端衰,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叠洗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后旅东,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灭抑,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年抵代,在試婚紗的時候發(fā)現(xiàn)自己被綠了腾节。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡荤牍,死狀恐怖案腺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情康吵,我是刑警寧澤劈榨,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站涎才,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏力九。R本人自食惡果不足惜耍铜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望跌前。 院中可真熱鬧棕兼,春花似錦、人聲如沸抵乓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灾炭。三九已至茎芋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜈出,已是汗流浹背田弥。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铡原,地道東北人偷厦。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像燕刻,于是被迫代替她去往敵國和親只泼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容