[懷舊并發01]如何正確結束Java線程

線程的啟動很簡單,但用戶可能隨時取消任務,怎么樣讓跑起來的線程正確地結束,這是今天要討論的話題。

使用標志位

很簡單地設置一個標志位,名稱就叫做isCancelled。啟動線程后,定期檢查這個標志位。如果isCancelled=true,那么線程就馬上結束。

public class MyThread implements Runnable{
    private volatile boolean isCancelled;
    
    public void run(){
        while(!isCancelled){
            //do something
        }
    }
    
    public void cancel(){   isCancelled=true;    }
}

注意的是,isCancelled需要為volatile,保證線程讀取時isCancelled是最新數據。

我以前經常用這種簡單方法,在大多時候也很有效,但并不完善。考慮下,如果線程執行的方法被阻塞,那么如何執行isCancelled的檢查呢?線程有可能永遠不會去檢查標志位,也就卡住了。

使用中斷

Java提供了中斷機制,Thread類下有三個重要方法。

  • public void interrupt()
  • public boolean isInterrupted()
  • public static boolean interrupted(); // 清除中斷標志,并返回原狀態

每個線程都有個boolean類型的中斷狀態。當使用Thread的interrupt()方法時,線程的中斷狀態會被設置為true。

下面的例子啟動了一個線程,循環執行打印一些信息。使用isInterrupted()方法判斷線程是否被中斷,如果是就結束線程。

public class InterruptedExample {

    public static void main(String[] args) throws Exception {
        InterruptedExample interruptedExample = new InterruptedExample();
        interruptedExample.start();
    }

    public void start() {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(3000);
            myThread.cancel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class MyThread extends Thread{

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println("test");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("interrupt");
                    //拋出InterruptedException后中斷標志被清除,標準做法是再次調用interrupt恢復中斷
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("stop");
        }

        public void cancel(){
            interrupt();
        }
    }
}

對線程調用interrupt()方法,不會真正中斷正在運行的線程,只是發出一個請求,由線程在合適時候結束自己。

例如Thread.sleep這個阻塞方法,接收到中斷請求,會拋出InterruptedException,讓上層代碼處理。這個時候,你可以什么都不做,但等于吞掉了中斷。因為拋出InterruptedException后,中斷標記會被重新設置為false!看sleep()的注釋,也強調了這點。

@throws  InterruptedException
     if any thread has interrupted the current thread. 
     The interrupted status of the current thread is 
     cleared when this exception is thrown.
public static native void sleep(long millis) throws InterruptedException;

記得這個規則:什么時候都不應該吞掉中斷!每個線程都應該有合適的方法響應中斷!

所以在InterruptedExample例子里,在接收到中斷請求時,標準做法是執行Thread.currentThread().interrupt()恢復中斷,讓線程退出。

從另一方面談起,你不能吞掉中斷,也不能中斷你不熟悉的線程。如果線程沒有響應中斷的方法,你無論調用多少次interrupt()方法,也像泥牛入海。

用Java庫的方法比自己寫的要好

自己手動調用interrupt()方法來中斷程序,OK。但是Java庫提供了一些類來實現中斷,更好更強大。

Executor框架提供了Java線程池的能力,ExecutorService擴展了Executor,提供了管理線程生命周期的關鍵能力。其中,ExecutorService.submit返回了Future對象來描述一個線程任務,它有一個cancel()方法。

下面的例子擴展了上面的InterruptedExample,要求線程在限定時間內得到結果,否則觸發超時停止。

public class InterruptByFuture {

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newSingleThreadExecutor();
        Future<?> task = es.submit(new MyThread());

        try {
            //限定時間獲取結果
            task.get(5, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            //超時觸發線程中止
            System.out.println("thread over time");
        } catch (ExecutionException e) {
            throw e;
        } finally {
            boolean mayInterruptIfRunning = true;
            task.cancel(mayInterruptIfRunning);
        }
    }

    private static class MyThread extends Thread {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {   
                try {
                    System.out.println("count");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("interrupt");
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("thread stop");
        }

        public void cancel() {
            interrupt();
        }
    }
}

Future的get方法可以傳入時間,如果限定時間內沒有得到結果,將會拋出TimeoutException。此時,可以調用Future的cancel()方法,對任務所在線程發出中斷請求。

cancel()有個參數mayInterruptIfRunning,表示任務是否能夠接收到中斷。

  • mayInterruptIfRunning=true時,任務如果在某個線程中運行,那么這個線程能夠被中斷;
  • mayInterruptIfRunning=false時,任務如果還未啟動,就不要運行它,應用于不處理中斷的任務

要注意,mayInterruptIfRunning=true表示線程能接收中斷,但線程是否實現了中斷不得而知。線程要正確響應中斷,才能真正被cancel。

線程池的shutdownNow()會嘗試停止池內所有在執行的線程,原理也是發出中斷請求。對于線程池的停止,下次新開一篇再講吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容