[懷舊并發(fā)01]如何正確結(jié)束Java線程

線程的啟動(dòng)很簡(jiǎn)單,但用戶可能隨時(shí)取消任務(wù)乾忱,怎么樣讓跑起來的線程正確地結(jié)束泪电,這是今天要討論的話題空盼。

使用標(biāo)志位

很簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志位,名稱就叫做isCancelled必孤。啟動(dòng)線程后,定期檢查這個(gè)標(biāo)志位。如果isCancelled=true仪召,那么線程就馬上結(jié)束。

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

注意的是松蒜,isCancelled需要為volatile扔茅,保證線程讀取時(shí)isCancelled是最新數(shù)據(jù)。

我以前經(jīng)常用這種簡(jiǎn)單方法秸苗,在大多時(shí)候也很有效召娜,但并不完善【ィ考慮下玖瘸,如果線程執(zhí)行的方法被阻塞,那么如何執(zhí)行isCancelled的檢查呢檀咙?線程有可能永遠(yuǎn)不會(huì)去檢查標(biāo)志位雅倒,也就卡住了。

使用中斷

Java提供了中斷機(jī)制弧可,Thread類下有三個(gè)重要方法蔑匣。

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

每個(gè)線程都有個(gè)boolean類型的中斷狀態(tài)棕诵。當(dāng)使用Thread的interrupt()方法時(shí)裁良,線程的中斷狀態(tài)會(huì)被設(shè)置為true。

下面的例子啟動(dòng)了一個(gè)線程校套,循環(huán)執(zhí)行打印一些信息价脾。使用isInterrupted()方法判斷線程是否被中斷,如果是就結(jié)束線程笛匙。

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后中斷標(biāo)志被清除侨把,標(biāo)準(zhǔn)做法是再次調(diào)用interrupt恢復(fù)中斷
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("stop");
        }

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

對(duì)線程調(diào)用interrupt()方法,不會(huì)真正中斷正在運(yùn)行的線程膳算,只是發(fā)出一個(gè)請(qǐng)求座硕,由線程在合適時(shí)候結(jié)束自己。

例如Thread.sleep這個(gè)阻塞方法涕蜂,接收到中斷請(qǐng)求华匾,會(huì)拋出InterruptedException,讓上層代碼處理。這個(gè)時(shí)候蜘拉,你可以什么都不做萨西,但等于吞掉了中斷。因?yàn)閽伋鯥nterruptedException后旭旭,中斷標(biāo)記會(huì)被重新設(shè)置為false谎脯!看sleep()的注釋,也強(qiáng)調(diào)了這點(diǎn)持寄。

@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;

記得這個(gè)規(guī)則:什么時(shí)候都不應(yīng)該吞掉中斷源梭!每個(gè)線程都應(yīng)該有合適的方法響應(yīng)中斷!

所以在InterruptedExample例子里稍味,在接收到中斷請(qǐng)求時(shí)废麻,標(biāo)準(zhǔn)做法是執(zhí)行Thread.currentThread().interrupt()恢復(fù)中斷,讓線程退出模庐。

從另一方面談起烛愧,你不能吞掉中斷,也不能中斷你不熟悉的線程掂碱。如果線程沒有響應(yīng)中斷的方法怜姿,你無論調(diào)用多少次interrupt()方法,也像泥牛入海疼燥。

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

自己手動(dòng)調(diào)用interrupt()方法來中斷程序沧卢,OK。但是Java庫提供了一些類來實(shí)現(xiàn)中斷悴了,更好更強(qiáng)大搏恤。

Executor框架提供了Java線程池的能力违寿,ExecutorService擴(kuò)展了Executor湃交,提供了管理線程生命周期的關(guān)鍵能力。其中藤巢,ExecutorService.submit返回了Future對(duì)象來描述一個(gè)線程任務(wù)搞莺,它有一個(gè)cancel()方法。

下面的例子擴(kuò)展了上面的InterruptedExample掂咒,要求線程在限定時(shí)間內(nèi)得到結(jié)果才沧,否則觸發(fā)超時(shí)停止。

public class InterruptByFuture {

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

        try {
            //限定時(shí)間獲取結(jié)果
            task.get(5, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            //超時(shí)觸發(fā)線程中止
            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方法可以傳入時(shí)間绍刮,如果限定時(shí)間內(nèi)沒有得到結(jié)果温圆,將會(huì)拋出TimeoutException。此時(shí)孩革,可以調(diào)用Future的cancel()方法岁歉,對(duì)任務(wù)所在線程發(fā)出中斷請(qǐng)求。

cancel()有個(gè)參數(shù)mayInterruptIfRunning膝蜈,表示任務(wù)是否能夠接收到中斷锅移。

  • mayInterruptIfRunning=true時(shí)熔掺,任務(wù)如果在某個(gè)線程中運(yùn)行,那么這個(gè)線程能夠被中斷非剃;
  • mayInterruptIfRunning=false時(shí)置逻,任務(wù)如果還未啟動(dòng),就不要運(yùn)行它备绽,應(yīng)用于不處理中斷的任務(wù)

要注意券坞,mayInterruptIfRunning=true表示線程能接收中斷,但線程是否實(shí)現(xiàn)了中斷不得而知肺素。線程要正確響應(yīng)中斷报慕,才能真正被cancel。

線程池的shutdownNow()會(huì)嘗試停止池內(nèi)所有在執(zhí)行的線程压怠,原理也是發(fā)出中斷請(qǐng)求眠冈。對(duì)于線程池的停止,下次新開一篇再講吧菌瘫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜗顽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雨让,更是在濱河造成了極大的恐慌雇盖,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栖忠,死亡現(xiàn)場(chǎng)離奇詭異崔挖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庵寞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門狸相,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捐川,你說我怎么就攤上這事脓鹃。” “怎么了古沥?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵瘸右,是天一觀的道長。 經(jīng)常有香客問我岩齿,道長太颤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任盹沈,我火速辦了婚禮龄章,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己瓦堵,他們只是感情好基协,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著菇用,像睡著了一般澜驮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惋鸥,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天杂穷,我揣著相機(jī)與錄音,去河邊找鬼卦绣。 笑死耐量,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的滤港。 我是一名探鬼主播廊蜒,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼溅漾!你這毒婦竟也來了山叮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤添履,失蹤者是張志新(化名)和其女友劉穎屁倔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暮胧,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锐借,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了往衷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钞翔。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炼绘,靈堂內(nèi)的尸體忽然破棺而出嗅战,到底是詐尸還是另有隱情,我是刑警寧澤俺亮,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站疟呐,受9級(jí)特大地震影響脚曾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜启具,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一本讥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦拷沸、人聲如沸色查。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秧了。三九已至,卻和暖如春序无,著一層夾襖步出監(jiān)牢的瞬間验毡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工帝嗡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晶通,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓哟玷,卻偏偏與公主長得像狮辽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巢寡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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

  • 一隘竭、并發(fā) 進(jìn)程:每個(gè)進(jìn)程都擁有自己的一套變量 線程:線程之間共享數(shù)據(jù) 1.線程 Java中為多線程任務(wù)提供了很多的...
    SeanMa閱讀 2,476評(píng)論 0 11
  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,655評(píng)論 2 17
  • 線程概述 線程與進(jìn)程 進(jìn)程 ?每個(gè)運(yùn)行中的任務(wù)(通常是程序)就是一個(gè)進(jìn)程讼渊。當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí)动看,即變成了一個(gè)進(jìn)...
    閩越布衣閱讀 1,012評(píng)論 1 7
  • java多線程 [TOC] 創(chuàng)建線程 直接調(diào)用Thread類或Runnable類的run方法并不會(huì) 創(chuàng)建線程,只會(huì)...
    蕩輕風(fēng)閱讀 490評(píng)論 0 0
  • 假期我不小心把腰給閃了。只是彎腰撿沙發(fā)上的小熊挨稿,就咔嚓一聲仇轻,腰不能動(dòng)。接下來整個(gè)人都不好了奶甘,感覺一下跨躍到老年篷店,腰...
    趙趙的不晚主義閱讀 881評(píng)論 0 3