7取消與關(guān)閉

概念

  • 中斷(Interruption),是一種協(xié)作機制革答,能夠使一個線程終止另一個線程的當(dāng)前工作。
  • 中斷只適合用于取消操作参淫,如果用在取消之外的其他操作則是不合適的;中斷是實現(xiàn)取消的最合理方式愧杯。

中斷是必要的

  • 我們很少希望某個任務(wù)涎才、線程或服務(wù)立即停止,因為這樣會使共享的數(shù)據(jù)結(jié)構(gòu)處于不一致的狀態(tài)力九;而使用中斷這種協(xié)作機制耍铜,當(dāng)需要停止時,任務(wù)首先會清除當(dāng)前正在執(zhí)行的工作畏邢,然后再結(jié)束业扒,因為任務(wù)本身的代碼比發(fā)出請求的代碼更清楚如何執(zhí)行清除工作。

行為好壞的程序的區(qū)別

  • 行為良好的程序能很完善地處理失敗舒萎、關(guān)閉和取消等情況程储。

取消任務(wù)概念

  • 外部代碼能在某個任務(wù)正常完成之前將其置為“完成”狀態(tài)

任務(wù)取消原因

  • 用戶請求取消(例如:用戶點擊取消按鈕)
  • 有時間限制的操作(例如:任務(wù)執(zhí)行超時蹭沛,則取消任務(wù))
  • 應(yīng)用程序事件(例如:一個任務(wù)的完成事件,導(dǎo)致其他任務(wù)取消)
  • 錯誤(例如:一個任務(wù)發(fā)生錯誤章鲤,導(dǎo)致其他任務(wù)取消)
  • 關(guān)閉(例如:程序運行結(jié)束摊灭,取消長運行的任務(wù))

任務(wù)取消策略

  • How:外部代碼如何請求取消任務(wù)
  • When:任務(wù)在何時檢查外部代碼是否已經(jīng)請求了取消
  • What:任務(wù)取消時應(yīng)該執(zhí)行哪些操作

線程中斷策略

  • 中斷策略規(guī)定線程如何解釋某個中斷請求
  • 最合理的中斷策略是某種線程級或服務(wù)級取消操作:
    • 盡快退出或拋出InterruptException異常
    • 在必要的時候進行清理(可以在線程中斷后進行清理工作,例如shutdown方法內(nèi)清理)
    • 通知某個所有者該線程已經(jīng)退出(標記被中斷線程的狀態(tài)為中斷)
  • 其他常用中斷策略:停止服務(wù)败徊、重新開始服務(wù)帚呼,對于包含這些非標準中斷策略的線程或線程池,只能應(yīng)用于知道這些策略的任務(wù)中皱蹦。

注:正如任務(wù)中應(yīng)該包含取消策略煤杀,線程同樣應(yīng)該包含中斷策略

Thread中斷相關(guān)操作

  • interrupt: 用于中斷目標線程
  • isInterrupted: 用于檢測目標線程是否會中斷
  • Thread.interrupted: 用于清除當(dāng)前線程的中斷狀態(tài),并返回之前的中斷狀態(tài)沪哺,這是清除中斷狀態(tài)的唯一方法

注1:如果目標線程已經(jīng)結(jié)束沈自,則 isInterrupted 始終返回false!

注2:調(diào)用interrupt并不意味著立即停止目標線程正在執(zhí)行的任務(wù)辜妓,而只是傳遞了請求中斷信息枯途!

支持中斷的阻塞庫方法

  • Thread.sleep
  • Thread.join
  • Object.wait

注:它們響應(yīng)中斷的操作包括:清除中斷狀態(tài),拋出InterruptedException籍滴。也就是說酪夷,收到InterruptedException時,中斷狀態(tài)已經(jīng)為 false 孽惰!

中斷請求的接收者

  • 任務(wù)和線程都是中斷請求的接收者晚岭,一個中斷請求到達時,意味著需要“取消任務(wù)”和“關(guān)閉工作者線程”
  • 任務(wù)不會在自己擁有的線程中運行(任務(wù)只是任務(wù)灰瞻,不是線程)腥例,任務(wù)是非線程所有者辅甥,在處理中斷請求時應(yīng)該小心的保存中斷狀態(tài)酝润,這樣線程擁有者才能對中斷做出響應(yīng),即使其他后續(xù)“非擁有者”也可以做出響應(yīng)璃弄。

任務(wù)響應(yīng)中斷的兩種方式

  • 傳遞異常InterruptedException
  • 恢復(fù)中斷狀態(tài)(調(diào)用Thread.interrupt方法)

注1:任務(wù)不應(yīng)該對執(zhí)行該任務(wù)的線程的中斷策略做出任何假設(shè)要销,除非該任務(wù)被專門設(shè)計為服務(wù)中運行,并且在這些服務(wù)中包含特定的中斷策略夏块!

注2:只有實現(xiàn)了線程中斷策略的代碼才可以屏蔽中斷請求疏咐,在常規(guī)的任務(wù)和庫代碼中都不應(yīng)該屏蔽中斷請求!

如何中斷線程

  • 線程只能由其所有者中斷脐供,所有者可以將線程的中斷策略信息封裝到某個合適的取消機制中浑塞,里如果關(guān)閉(shutdown)方法。

任務(wù)對中斷的響應(yīng)

  • 支持取消且調(diào)用了中斷阻塞方法的任務(wù)政己,應(yīng)該盡快取消任務(wù)酌壕,恢復(fù)中斷狀態(tài)
  • 不支持取消但在循環(huán)中調(diào)用中斷阻塞方法的任務(wù),應(yīng)該在本地保存中斷狀態(tài),并在任務(wù)返回前恢復(fù)中斷狀態(tài)卵牍,而不是在捕獲中斷時就恢復(fù)果港,那樣容易引起死循環(huán)
  • 不調(diào)用中斷阻塞方法的任務(wù),在循環(huán)中輪詢當(dāng)前線程的中斷狀態(tài)來響應(yīng)中斷

Java中斷機制的優(yōu)點

  • Java中斷為非搶占式中斷糊昙,通過推遲中斷請求的處理辛掠,開發(fā)人員能指定靈活的終端策略,從而使應(yīng)用程序在響應(yīng)性和建壯性之間實現(xiàn)合理的平衡释牺。

標準取消操作示例

package cn.weicm.cancel;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * <b>Time:&#9;2018/8/3 13:52</b><br/>
 * <b>Auth:&#9;weicm</b><br/>
 * <br/>
 * <b>Desp:&#9;在外部線程中安排中斷</b><br/>
 * <br/>
 * <b>優(yōu)點:</b><br/>
 * <ul>
 *     <li>能從任務(wù)中拋出未檢查異常萝衩,異常會被timedRun的調(diào)用者捕獲</li>
 * </ul>
 * <br/>
 * <b>缺點:</b><br/>
 * <ul>
 *     <li>在中斷線程時,不了解執(zhí)行任務(wù)的線程的中斷策略没咙,因為timedRun可能在任何線程中運行</li>
 *     <li>如果任務(wù)不響應(yīng)中斷欠气,那么timedRun會在任務(wù)結(jié)束時才返回钥弯,此時可能已經(jīng)超過了指定時限豹储,這會對調(diào)用者帶來負面影響</li>
 * </ul>
 */
public class TimedRun1 {
    private static final ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);

    public static void main(String[] args) {
        try {
            timedRun(() -> {
                System.out.println("Task start ...");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    System.out.println("Task is canceled!");
                    //假設(shè)任務(wù)不響應(yīng)中斷,即不拋出InterruptedException喳资,也不通過Thread.interrupt()恢復(fù)中斷狀態(tài)袁梗,而是直接退出
                    return;
                }
                System.out.println("Task end ...");
            }, 1, TimeUnit.SECONDS);
        } finally {
            ses.shutdown();
        }

    }


    /**
     * <b>Time:&#9;2018/8/3 14:20</b><br/>
     * <b>Auth:&#9;weicm</b><br/>
     * <br/>
     * <b>Desp:&#9;在制定時限內(nèi)運行任務(wù)宜鸯,超過時限則取消任務(wù)</b><br/>
     *
     * @param task 任務(wù)
     * @param timeout 時限數(shù)量
     * @param unit 時限單位
     */
    static void timedRun(Runnable task, long timeout, TimeUnit unit) {
        //獲取目標任務(wù)所在線程,此時該線程可能是任意線程
        Thread currentThread = Thread.currentThread();
        //啟動中斷任務(wù)
        ScheduledFuture<?> future = ses.schedule(() -> {
            currentThread.interrupt();
        }, timeout, unit);
        task.run();
        //任務(wù)結(jié)束后遮怜,取消掉中斷任務(wù)淋袖,因為此時目標任務(wù)已經(jīng)結(jié)束,中斷任務(wù)已經(jīng)沒有存在的意義了
        future.cancel(true);
    }
}
package cn.weicm.cancel;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * <b>Time:&#9;2018/8/3 15:36</b><br/>
 * <b>Auth:&#9;weicm</b><br/>
 * <br/>
 * <b>Desp:&#9;在專門的線程中中斷任務(wù)</b><br/>
 * </br>
 * <b>優(yōu)點:&#9;</b><br/>
 * <ul>
 *     <li>解決了TimedRun1的所有缺點</li>
 * </ul>
 * </br>
 * <b>缺點:&#9;</b><br/>
 * <ul>
 *     <li>由于join的不足锯梁,無法知道任務(wù)是因為線程正常退出而返回還是因為join超時而返回</li>
 * </ul>
 */
public class TimedRun2 {
    private static final ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);

    public static void main(String[] args) throws Throwable {
        try {
            timedRun(() -> {
                System.out.println("Task start ...");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    System.out.println("Task is canceled!");
                    //假設(shè)任務(wù)不響應(yīng)中斷即碗,即不拋出InterruptedException,也不通過Thread.interrupt()恢復(fù)中斷狀態(tài)陌凳,而是直接退出
                    return;
                }
                System.out.println("Task end ...");
            }, 1, TimeUnit.SECONDS);
        } finally {
            ses.shutdown();
        }
    }

    /**
     * <b>Time:&#9;2018/8/3 14:20</b><br/>
     * <b>Auth:&#9;weicm</b><br/>
     * <br/>
     * <b>Desp:&#9;在制定時限內(nèi)運行任務(wù)剥懒,超過時限則取消任務(wù)</b><br/>
     *
     * @param task 任務(wù)
     * @param timeout 時限數(shù)量
     * @param unit 時限單位
     */
    static void timedRun(Runnable task, long timeout, TimeUnit unit) throws Throwable {
        /**
         * <b>Time:&#9;2018/8/3 14:42</b><br/>
         * <b>Auth:&#9;weicm</b><br/>
         * <br/>
         * <b>Desp:&#9;裝執(zhí)行線程的中斷策略</b><br/>
         */
        class ThrowableTask implements Runnable {
            private volatile Throwable e;

            @Override
            public void run() {
                try {
                    task.run();
                } catch (Throwable e) {
                    //中斷策略: 錄任務(wù)的運行時異常,以便稍后重新拋出該異常合敦,并結(jié)束執(zhí)行線程
                    this.e = e;
                }
            }

            /**
             * <b>Time:&#9;2018/8/3 15:33</b><br/>
             * <b>Auth:&#9;weicm</b><br/>
             * <br/>
             * <b>Desp:&#9;重新拋出目標任務(wù)運行過程中可能發(fā)生的異常</b><br/>
             *
             * @throws Throwable
             */
            public void rethrow() throws Throwable {
                if (null != e)
                    throw e;
            }
        }
        //將目標任務(wù)運行在明確中斷策略的執(zhí)行線程里
        ThrowableTask t = new ThrowableTask();
        Thread taskThread = new Thread(t);
        taskThread.start();
        //啟動中斷任務(wù)
        ScheduledFuture<?> future = ses.schedule(() -> {
            taskThread.interrupt();
        }, timeout, unit);
        taskThread.join(unit.toMillis(timeout));
        //任務(wù)結(jié)束后初橘,取消掉中斷任務(wù),因為此時目標任務(wù)已經(jīng)結(jié)束充岛,中斷任務(wù)已經(jīng)沒有存在的意義了
        future.cancel(true);
        //重新拋出任務(wù)執(zhí)行過程中發(fā)生的異常
        t.rethrow();
    }
}
package cn.weicm.cancel;

import java.util.concurrent.*;

/**
 * <b>Time:&#9;2018/8/3 16:38</b><br/>
 * <b>Auth:&#9;weicm</b><br/>
 * <br/>
 * <b>Desp:&#9;通過Future來取消任務(wù)</b><br/>
 * </br>
 * <b>優(yōu)點:&#9;</b><br/>
 * <ul>
 *     <li>解決TimedRun2的缺點保檐,可以區(qū)分任務(wù)是如何結(jié)束的</li>
 * </ul>
 * </br>
 * <b>關(guān)于Futrue.cancel:&#9;針對任務(wù)的三種狀態(tài)</b><br/>
 * <ul>
 *     <li>等待狀態(tài):此時不管參數(shù)傳入的是true還是false,任務(wù)都會被標記為取消崔梗,任務(wù)依然保存在隊列中夜只,但當(dāng)輪詢到此任務(wù)時會直接跳過</li>
 *     <li>運行狀態(tài):此時參數(shù)傳入true會中斷正在執(zhí)行的任務(wù);傳入false則不會中斷任務(wù)蒜魄,而是讓任務(wù)繼續(xù)運行直到結(jié)束</li>
 *     <li>完成狀態(tài):此時不管參數(shù)傳入的是true還是false扔亥,cancel都不起作用爪膊,因為任務(wù)已經(jīng)完成了</li>
 * </ul>
 */
public class TimedRun3 {
    private static final ExecutorService es = Executors.newSingleThreadExecutor();
    public static void main(String[] args) throws Exception{
        try {
            timedRun(() -> {
                System.out.println("Task start ...");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    System.out.println("Task is canceled!");
                    //假設(shè)任務(wù)不響應(yīng)中斷,即不拋出InterruptedException砸王,也不通過Thread.interrupt()恢復(fù)中斷狀態(tài)推盛,而是直接退出
                    return;
                }
                System.out.println("Task end ...");
            }, 1, TimeUnit.SECONDS);
        } finally {
            es.shutdown();
        }
    }

    /**
     * <b>Time:&#9;2018/8/3 14:20</b><br/>
     * <b>Auth:&#9;weicm</b><br/>
     * <br/>
     * <b>Desp:&#9;在制定時限內(nèi)運行任務(wù),超過時限則取消任務(wù)</b><br/>
     *
     * @param task 任務(wù)
     * @param timeout 時限數(shù)量
     * @param unit 時限單位
     */
    static void timedRun(Runnable task, long timeout, TimeUnit unit) throws Exception {
        Future<?> future = es.submit(task);
        try {
            future.get(timeout, unit);
        } catch (InterruptedException e) {
            //當(dāng)前線程被中斷谦铃,恢復(fù)中斷狀態(tài)以傳遞中斷信息
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            //目標任務(wù)執(zhí)行過程發(fā)生運行時異常耘成,直接拋出
            throw e;
        } catch (TimeoutException e) {
            //目標任務(wù)執(zhí)行超時,接下來取消任務(wù)驹闰,因為已經(jīng)不需要結(jié)果了
        } finally {
            //如果任務(wù)已經(jīng)運行完瘪菌,則取消操作不產(chǎn)生任何效果;如果任務(wù)由與異常而終止嘹朗,不管什么異常师妙,則取消任務(wù),因為已經(jīng)不需要結(jié)果了
            //取消那些不在需要結(jié)果的任務(wù)是一種良好的習(xí)慣屹培!
            future.cancel(true);
        }
    }
}

不可中斷的阻塞

  • Thread.interrupt對于執(zhí)行不可中斷的操作而阻塞的線程默穴,只能設(shè)置線程的中斷狀態(tài),除此之外沒有其他任何作用褪秀;但可以使用類似于中斷的手段來停止這些線程蓄诽,但是必須知道線程阻塞的原因。

常見不可中斷的阻塞操作

  • Java.io包中的同步Socket I/O: InputStream.read和OutputStream.write媒吗,可以關(guān)閉套接字(Socket.close)使他們拋出SocketException仑氛。
  • Java.io包中的同步I/O: 當(dāng)中斷一個正在InterruptibleChannel上等待的線程時,將拋出ClosedByInterruptException并關(guān)閉鏈路闸英,這還會使得其他在該鏈路上阻塞的線程同樣拋出ClosedByInterruptException锯岖。當(dāng)關(guān)閉一個InterruptibleChannel時,將導(dǎo)致所有在鏈路操作上阻塞的線程都拋出AsynchronousCloseException甫何。
  • Selector的異步I/O: 如果一個線程在調(diào)用Selector.select方法時阻塞了出吹,那么調(diào)用close或wakeup方法會使線程拋出ClosedSelectorException并提前返回。
  • 獲取某個鎖: 如果一個線程由與等待某個內(nèi)置鎖而阻塞沛豌,那么將無法響應(yīng)中斷趋箩,因為線程認為它肯定會獲得鎖,所以將不會理會中斷請求加派。但是Lock類中提供了lockInterruptiblely方法,該方法允許在等待一個鎖的同時仍能響應(yīng)中斷跳芳。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芍锦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子飞盆,更是在濱河造成了極大的恐慌娄琉,老刑警劉巖次乓,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異孽水,居然都是意外死亡票腰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門女气,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杏慰,“玉大人,你說我怎么就攤上這事炼鞠≡道模” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵谒主,是天一觀的道長朝扼。 經(jīng)常有香客問我,道長霎肯,這世上最難降的妖魔是什么擎颖? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮观游,結(jié)果婚禮上肠仪,老公的妹妹穿的比我還像新娘。我一直安慰自己备典,他們只是感情好异旧,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著提佣,像睡著了一般吮蛹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拌屏,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天潮针,我揣著相機與錄音,去河邊找鬼倚喂。 笑死每篷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的端圈。 我是一名探鬼主播焦读,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舱权!你這毒婦竟也來了矗晃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宴倍,失蹤者是張志新(化名)和其女友劉穎张症,沒想到半個月后仓技,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡俗他,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年脖捻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兆衅。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡地沮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涯保,到底是詐尸還是另有隱情诉濒,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布夕春,位于F島的核電站未荒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏及志。R本人自食惡果不足惜片排,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望速侈。 院中可真熱鬧率寡,春花似錦、人聲如沸倚搬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽每界。三九已至捅僵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眨层,已是汗流浹背庙楚。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趴樱,地道東北人馒闷。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像叁征,于是被迫代替她去往敵國和親纳账。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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