Java “優(yōu)雅”地中斷線程(實(shí)踐篇)

前言

線程并發(fā)系列文章:

Java 線程基礎(chǔ)
Java 線程狀態(tài)
Java “優(yōu)雅”地中斷線程-實(shí)踐篇
Java “優(yōu)雅”地中斷線程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有誤
Java Unsafe/CAS/LockSupport 應(yīng)用與原理
Java 并發(fā)"鎖"的本質(zhì)(一步步實(shí)現(xiàn)鎖)
Java Synchronized實(shí)現(xiàn)互斥之應(yīng)用與源碼初探
Java 對(duì)象頭分析與使用(Synchronized相關(guān))
Java Synchronized 偏向鎖/輕量級(jí)鎖/重量級(jí)鎖的演變過(guò)程
Java Synchronized 重量級(jí)鎖原理深入剖析上(互斥篇)
Java Synchronized 重量級(jí)鎖原理深入剖析下(同步篇)
Java并發(fā)之 AQS 深入解析(上)
Java并發(fā)之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 詳解
Java 并發(fā)之 ReentrantLock 深入分析(與Synchronized區(qū)別)
Java 并發(fā)之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(應(yīng)用篇)
最詳細(xì)的圖文解析Java各種鎖(終極篇)
線程池必懂系列

在Android開發(fā)中,不可避免的會(huì)用到線程來(lái)執(zhí)行耗時(shí)任務(wù)苫幢,那如果我們想在中途停止/中斷任務(wù)的執(zhí)行究飞,該怎么辦呢屡谐?先來(lái)看看一個(gè)簡(jiǎn)單的線程。

    private Thread threadOne = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                while(true) {
                    Log.d(TAG, "thread isAlive:" + threadOne.isAlive());
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                Log.d(TAG, e.getClass().toString());
                Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
            }
        }
    });

正常運(yùn)行打印結(jié)果:


image.png

當(dāng)使用interrupt()方法中斷該線程時(shí)斯辰,打印如下:


image.png

可以看出八回,調(diào)用interrupt()后萄焦,會(huì)捕獲名為“InterruptedException”的異常,但是接下來(lái)的發(fā)現(xiàn)線程還存活凤瘦,這是怎么回事呢宿礁?既然線程能夠被中斷,那么是否提供查詢中斷狀態(tài)的方法呢蔬芥?通過(guò)查看api我們發(fā)現(xiàn)梆靖,thread.isInterrupted()可以查看線程的中斷狀態(tài),因此我們?cè)偌右粋€(gè)打颖仕小:

                Log.d(TAG, e.getClass().toString());
                Log.d(TAG, "thread isInterrupted::" + threadOne.isInterrupted());
                Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());

然而中斷狀態(tài)位依然是“未被中斷”返吻。這與我們想象的不太一樣赂鲤,因此回想一下是哪個(gè)方法拋出了異常藤为,發(fā)現(xiàn)是sleep方法。

// BEGIN Android-changed: Implement sleep() methods using a shared native implementation.
    public static void sleep(long millis) throws InterruptedException {
        sleep(millis, 0);
    }

我們先把sleep()方法注釋掉望浩,再運(yùn)行

    private Thread threadOne = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                while (!threadOne.isInterrupted()) {
                    Log.d(TAG, "thread isAlive:" + threadOne.isAlive());
                }
                Log.d(TAG, "break while thread isAlive:" + threadOne.isAlive());
                Log.d(TAG, "break while thread isInterrupted::" + threadOne.isInterrupted());
            } catch (Exception e) {
                Log.d(TAG, e.getClass().toString());
                Log.d(TAG, "thread isInterrupted::" + threadOne.isInterrupted());
                Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
            }
        }
    });
image.png

這次的結(jié)果比較符合我們的“直觀想象”, 線程還是存活次酌,但中斷狀態(tài)位標(biāo)記位為true恨课。

從上面兩個(gè)兩個(gè)例子可知:
1.中斷正在阻塞(sleep)的線程,會(huì)拋出InterruptedException異常岳服,中斷標(biāo)記位為false剂公。
2.中斷未被阻塞的線程,中斷標(biāo)記位會(huì)被置為true吊宋。

那么針對(duì)正在阻塞的線程纲辽,我們只需要捕獲到InterruptedException異常就退出線程執(zhí)行,對(duì)于未被阻塞的線程,判斷中斷標(biāo)記是否為true拖吼,若是則退出線程執(zhí)行鳞上。當(dāng)然我們線程里如果調(diào)用了其它方法,不確定其它方法阻塞與否吊档,因此可以將這兩種判斷結(jié)合起來(lái)篙议,如下:

    private Thread threadOne = new Thread(new Runnable() {
        @Override
        public void run() {

            try {
                while (!threadOne.isInterrupted()) {
                    doSomething();
                }

            } catch (Exception e) {
                if (e instanceof InterruptedException) {
                    isInterrupted = true;
                }
            }
        }
    });

也許你會(huì)疑惑說(shuō)你上面的線程都是在while里跑,如果線程只走一次怠硼,怎么中斷呢鬼贱?只能盡可能在每個(gè)關(guān)鍵之處停止其執(zhí)行。

    private Thread threadOne = new Thread(new Runnable() {
        @Override
        public void run() {

            try {
                if (!threadOne.isInterrupted())
                    doSomething1();
                else
                    return;

                if (!threadOne.isInterrupted())
                    doSomething2();
                else
                    return;

                if (!threadOne.isInterrupted())
                    doSomething3();
                else
                    return;

            } catch (Exception e) {
                
            }
        }
    });

除了sleep方法之外香璃,還有其它方法會(huì)拋出異常不这难?實(shí)際上,在Thread源碼里對(duì)此都有解釋葡秒,我們來(lái)看看源碼怎么說(shuō)的姻乓。

    /**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>

總結(jié)來(lái)說(shuō):

  • 在線程里使用sleep、wait眯牧、join等方法蹋岩,當(dāng)線程被中斷時(shí),中斷狀態(tài)位會(huì)被重置為false炸站,并且拋出InterruptedException異常(這也是為什么我們第一個(gè)例子里thread.isInterrupted()為false的原因)
  • 在線程里使用nio InterruptibleChannel接口時(shí)星澳,當(dāng)線程被中斷時(shí),中斷狀態(tài)位會(huì)被重置為true旱易,并且拋出ClosedByInterruptException異常
  • 在線程里使用nio Selector時(shí)禁偎,當(dāng)線程被中斷時(shí),中斷狀態(tài)位會(huì)被重置為true
  • 如不屬于上述條件阀坏,則中斷狀態(tài)位會(huì)被重置為true(對(duì)應(yīng)我們上面說(shuō)的沒(méi)有阻塞的情況)

thread.isInterrupted() 和 Thread.interrupted()區(qū)別
thread.isInterrupted()是對(duì)象方法如暖,表示thread的中斷狀態(tài)。Thread.interrupted()是靜態(tài)方法忌堂,表示當(dāng)前線程的中斷狀態(tài)盒至,舉個(gè)例子:

        Log.d(TAG, " curThread is:" + Thread.currentThread().getName());
        Log.d(TAG, " Thread.currentThread().isInterrupted() before :" + Thread.currentThread().isInterrupted());
        Log.d(TAG, " Thread.interrupted() before :" + Thread.interrupted());
        Log.d(TAG, " threadOne.isInterrupted() before :" + threadOne.isInterrupted());
        Thread.currentThread().interrupt();
        Log.d(TAG, " Thread.currentThread().isInterrupted() after:" + Thread.currentThread().isInterrupted());
        Log.d(TAG, " Thread.interrupted() after :" + Thread.interrupted());
        Log.d(TAG, " Thread.currentThread().isInterrupted() after2:" + Thread.currentThread().isInterrupted());
        Log.d(TAG, " threadOne.isInterrupted() after :" + threadOne.isInterrupted());
image.png

從上面可以看出來(lái),Thread.interrupted()調(diào)用后會(huì)重置中斷狀態(tài)為false士修,而thread.isInterrupted()卻不會(huì)枷遂。

總結(jié)

1、線程正在執(zhí)行sleep棋嘲、join酒唉、wait等方法,此時(shí)線程處在WAITING/TIMED_WAITING狀態(tài)沸移,當(dāng)執(zhí)行thread.interrupt()痪伦,那么會(huì)拋出InterruptedException異常侄榴,線程中斷標(biāo)記位為false,線程停止運(yùn)行网沾;
2癞蚕、線程處在RUNNABLE狀態(tài),當(dāng)執(zhí)行thread.interrupt()辉哥,不會(huì)拋出異常桦山,線程中斷標(biāo)記位為true,線程未停止運(yùn)行证薇;
3度苔、如果線程處在BLOCKED(Synchronized爭(zhēng)搶鎖)狀態(tài),當(dāng)執(zhí)行thread.interrupt()浑度,不會(huì)拋出異常,線程中斷標(biāo)記位為true鸦概,線程未停止運(yùn)行(這點(diǎn)也說(shuō)明了Synchronized不可打斷)

更多關(guān)于線程狀態(tài)的問(wèn)題請(qǐng)移步:Java 線程狀態(tài)

您若喜歡箩张,請(qǐng)點(diǎn)贊、關(guān)注窗市,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

持續(xù)更新中先慷,和我一起步步為營(yíng)系統(tǒng)、深入學(xué)習(xí)Android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咨察,一起剝皮案震驚了整個(gè)濱河市论熙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摄狱,老刑警劉巖脓诡,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異媒役,居然都是意外死亡祝谚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門酣衷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)交惯,“玉大人,你說(shuō)我怎么就攤上這事穿仪∠” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵啊片,是天一觀的道長(zhǎng)只锻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)钠龙,這世上最難降的妖魔是什么炬藤? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任御铃,我火速辦了婚禮,結(jié)果婚禮上沈矿,老公的妹妹穿的比我還像新娘上真。我一直安慰自己,他們只是感情好羹膳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布睡互。 她就那樣靜靜地躺著,像睡著了一般陵像。 火紅的嫁衣襯著肌膚如雪就珠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天醒颖,我揣著相機(jī)與錄音妻怎,去河邊找鬼。 笑死泞歉,一個(gè)胖子當(dāng)著我的面吹牛逼侦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腰耙,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼榛丢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了挺庞?” 一聲冷哼從身側(cè)響起晰赞,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎选侨,沒(méi)想到半個(gè)月后掖鱼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侵俗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年锨用,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隘谣。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡增拥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寻歧,到底是詐尸還是另有隱情掌栅,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布码泛,位于F島的核電站猾封,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏噪珊。R本人自食惡果不足惜晌缘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一齐莲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磷箕,春花似錦选酗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至空繁,卻和暖如春殿衰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盛泡。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工闷祥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饭于。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓蜀踏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掰吕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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