線程的異常處理機(jī)制

前言

分析線程對異常的處理機(jī)制避归,首先要了解Java自身的異常處理機(jī)制,關(guān)于 try笨奠、catch袭蝗、finally、throw般婆、throws 這些關(guān)鍵字及 未檢查異常檢查異常 等基礎(chǔ)不進(jìn)行展開到腥,本文主要討論對異常的處理。

未捕獲異常去哪里了蔚袍?

一個異常被拋出后左电,如果沒有被捕獲處理,那么會一直向上拋出页响,直到被main()/Thread.run()拋出。異常被main()/Thread.run() 拋出后段誊,會由虛擬機(jī)調(diào)用Thread 的 dispatchUncaughtException方法:

    /**
     * 向 handler 分派未捕獲的異常闰蚕。 該方法僅由JVM調(diào)用。
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

    // 獲取未捕獲異常處理的 handler连舍,如果沒有設(shè)置則返回當(dāng)前線程所屬的ThreadGroup
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

ThreadGroup繼承自 UncaughtExceptionHandler没陡,實現(xiàn)了默認(rèn)的處理方法:

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) { // 父級優(yōu)先處理
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) { // 沒有配置handler時的處理方式
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

當(dāng)有異常被main()/Thread.run() 拋出后,JVM會調(diào)用 dispatchUncaughtException 來尋找handler進(jìn)行處理。而如果沒有自定義hanlder進(jìn)行處理盼玄,則會調(diào)用 System.err 進(jìn)行輸出贴彼。

線程對異常的處理

本文將線程實現(xiàn)分為兩種,無返回值的Runnable實現(xiàn) 和 有返回值的FutureTask實現(xiàn)埃儿。然后分為 Thread 器仗、ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 三種情況來討論異常的處理。

一童番、Thread 異常處理

Thread 線程的有很多種實現(xiàn)形式精钮,基于 Runnable 的沒有返回值的線程,無法在主線程感知到異常剃斧,沒有捕獲的異常只會輸出到控制臺轨香,如果沒有捕獲異常,進(jìn)行處理或日志記錄幼东,會造成異常丟失臂容。有返回值的 FutureTask ,未捕獲的異常根蟹,會在獲取返回值時傳遞給主線程脓杉。

1. 主線程無法捕獲子線程異常

代碼一:

public static void catchInMainThread(){
    try {
        Thread thread = new Thread(() -> {
            System.out.println( 1 / 0);
        });
        thread.start();
    } catch (Exception e1) {
        System.out.println("catched exception in main thread");
    }
}
// 代碼一輸出
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at ThreadExecptionTest.lambda$0(ThreadExecptionTest.java:14)
    at java.lang.Thread.run(Thread.java:745)

上述代碼無法捕獲到異常,根據(jù)開篇介紹的默認(rèn)處理機(jī)制娜亿,異常在子線程的Thread.run()方法中拋出后丽已,JVM會調(diào)用 dispatchUncaughtException 方法最終輸出到控制臺。

2. 子線程處理Runnable異常

代碼二 :

public static void catchInRunThread() {
    Thread thread = new Thread(() -> {
        try {
            System.out.println(1 / 0);
        } catch (Exception e1) {
            // 異常處理买决,日志記錄等
            System.out.println("catched exception in child thread");
        }
    });
    thread.start();
}
// 代碼二輸出結(jié)果
catched exception in child thread

代碼二展示了線程內(nèi)按需處理異常的方式沛婴,這是一種推薦的線程內(nèi)異常處理方法。

3. 主線程處理FutureTask異常
public static void catchInFuture() {
    FutureTask<Integer> futureTask = new FutureTask<>(() ->  {
        return 1/0;
    });
    
    Thread thread = new Thread(futureTask);
    thread.start();
    try {
        futureTask.get();
    } catch (InterruptedException | ExecutionException e) {
        System.out.println("catched exception in main thread by future");
//          e.printStackTrace();
    }
}

輸出如下:

catched exception in main thread by future

FutureTask 的異常成功在主線程里處理督赤。

4. 配置默認(rèn)線程異常處理方式

代碼三:

public static void catchByExceptionHandler() {
    Thread thread = new Thread(() -> {
            System.out.println(1 / 0);
    });
    UncaughtExceptionHandler eh = new MyUncaughtExceptionHandler();
    thread.setUncaughtExceptionHandler(eh);
    thread.start();
}

static class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 異常處理嘁灯,日志記錄等
        System.out.println("catched exception in ExceptionHandler");
    }
    
}
// 代碼三輸出:
catched exception in ExceptionHandler

使用UncaughtExceptionHandler 來處理未捕獲異常也是一個可參考的方案。

二躲舌、ThreadPoolExecutor 異常處理

public class ThreadPoolExecutorExceptionTest {

    public static void main(String[] args) throws InterruptedException, IOException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        executeThreadPoolExecutor(executor);
        Thread.sleep(100);
        submitThreadPoolExecutorWithOutGet(executor);
        Thread.sleep(100);
        submitThreadPoolExecutorAndGet(executor);
        Thread.sleep(100);
        executor.shutdown();
    }
    // Runnable子線程沒有捕獲異常
    static void executeThreadPoolExecutor(ThreadPoolExecutor executor) {
        System.out.println("\n*****executeThreadPoolExecutor*****");
        executor.execute(() -> {
            System.out.println(1 / 0);
        });
    }
    // Callable不獲取返回值
    static void submitThreadPoolExecutorWithOutGet(ThreadPoolExecutor executor) {
        System.out.println("\n*****submitThreadPoolExecutorWithOutGet*****");
        executor.submit(() -> {
            System.out.println(1 / 0);
        });
    }
    // Callable獲取返回值
    static void submitThreadPoolExecutorAndGet(ThreadPoolExecutor executor) {
        System.out.println("\n*****submitThreadPoolExecutorAndGet*****");
        Future<?> future = executor.submit(() -> {
            System.out.println(1 / 0);
        });

        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("catched exception in main thread by future");
//          e.printStackTrace();
        }
    }
}

上面展示了三種ThreadPoolExecutor提交任務(wù)的形式丑婿,輸出如下:

*****executeThreadPoolExecutor*****
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at test.ThreadTest.lambda$1(ThreadTest.java:65)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

*****submitThreadPoolExecutorWithOutGet*****

*****submitThreadPoolExecutorAndGet*****
catched exception in main thread by future

直接用execute執(zhí)行Runnable方法,如果主線程里的異常未捕獲没卸,將被默認(rèn)handler輸出到控制臺羹奉,與Thread執(zhí)行任務(wù)類似。如果使用submit執(zhí)行FutureTask任務(wù)约计,而不調(diào)用get方法獲取返回值诀拭,則會造成異常丟失現(xiàn)象。使用FutureTask只有調(diào)用get方法才能感知到子線程是否正常執(zhí)行煤蚌。因此耕挨,為了確保任務(wù)順利執(zhí)行细卧,需使用get方法確認(rèn)返回結(jié)果是否有異常拋出。

三筒占、ScheduledThreadPoolExecutor 異常處理

之所以要將ScheduledThreadPoolExecutor 線程池單獨拿出來講贪庙,是因為ScheduledThreadPoolExecutor 的調(diào)度方法與普通線程池執(zhí)行方法不同,有著特殊的規(guī)則翰苫。
ScheduledThreadPoolExecutor 有個三個調(diào)度方法:schedule止邮、scheduleAtFixedRatescheduleWithFixedDelay革骨,schedule方法有個重載方法农尖,可以傳Callable執(zhí)行,詳情可以參閱線程池之ScheduledThreadPoolExecutor概述良哲。

image.png
謹(jǐn)防 ScheduledThreadPoolExecutor 異常丟失

從上圖我們可以看到盛卡,這幾個方法都有返回值為 ScheduledFuture ,根據(jù)前面的分析我們知道有返回值的Future可以在獲取返回值是捕獲到異常筑凫,不獲取返回值就會造成異常丟失滑沧。我們來驗證一下:

public class ScheduledPoolExecptionTest {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        schedule(executor);
    }
    
    static void schedule(ScheduledThreadPoolExecutor executor) {
        executor.schedule(() -> {
            int n = 1/0; // 應(yīng)該拋出異常
        }, 10, TimeUnit.NANOSECONDS);
    }
}

用ScheduledThreadPoolExecutor的schedule方法執(zhí)行Runnable,上述代碼沒有任何輸出巍实,異常消失了滓技。這是平時我們使用調(diào)度線程池很容易犯的一個錯誤,這種異常消失在生產(chǎn)環(huán)境中將產(chǎn)生難以發(fā)現(xiàn)的問題棚潦。
ScheduledThreadPoolExecutor 的其他幾個調(diào)度方法也會產(chǎn)生類似的問題令漂。由于我們執(zhí)行的是Runnable任務(wù),往往不會調(diào)用future的get方法丸边,因此在開發(fā)中叠必,應(yīng)該多加小心這種異常吞并。避免產(chǎn)生此問題妹窖,盡量在線程內(nèi)捕獲異常并處理纬朝;
ScheduledThreadPoolExecutor 的execute方法和submit繼承自
ThreadPoolExecutor,異常處理機(jī)制一樣骄呼。

總結(jié)

不管是Thread 共苛、ThreadPoolExecutor 還是ScheduledThreadPoolExecutor,都應(yīng)秉承線程內(nèi)部處理的原則蜓萄,做到自己的事情自己辦隅茎。正確的處理、記錄異常嫉沽,及時的發(fā)現(xiàn)問題患膛,避免不必要的麻煩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耻蛇,一起剝皮案震驚了整個濱河市踪蹬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臣咖,老刑警劉巖跃捣,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異夺蛇,居然都是意外死亡疚漆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門刁赦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娶聘,“玉大人,你說我怎么就攤上這事甚脉⊥枭” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵牺氨,是天一觀的道長狡耻。 經(jīng)常有香客問我,道長猴凹,這世上最難降的妖魔是什么夷狰? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮郊霎,結(jié)果婚禮上沼头,老公的妹妹穿的比我還像新娘。我一直安慰自己书劝,他們只是感情好进倍,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著庄撮,像睡著了一般背捌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洞斯,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天毡庆,我揣著相機(jī)與錄音,去河邊找鬼烙如。 笑死么抗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亚铁。 我是一名探鬼主播蝇刀,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼徘溢!你這毒婦竟也來了吞琐?” 一聲冷哼從身側(cè)響起捆探,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎站粟,沒想到半個月后黍图,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡奴烙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年助被,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片切诀。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡揩环,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幅虑,到底是詐尸還是另有隱情丰滑,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布翘单,位于F島的核電站吨枉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏哄芜。R本人自食惡果不足惜貌亭,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望认臊。 院中可真熱鬧圃庭,春花似錦、人聲如沸失晴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涂屁。三九已至书在,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拆又,已是汗流浹背儒旬。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留帖族,地道東北人栈源。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像竖般,于是被迫代替她去往敵國和親甚垦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

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

  • 執(zhí)行多線程并發(fā)任務(wù)的時候,如果任務(wù)類型相同艰亮,一般會考慮使用線程池闭翩,一方面利用了并發(fā)的優(yōu)勢,一方面避免創(chuàng)建大量線程得...
    德彪閱讀 23,434評論 2 19
  • 一迄埃、前言 線程池技術(shù)是服務(wù)器端開發(fā)中常用的技術(shù)男杈。不論是直接還是間接,各種服務(wù)器端功能的執(zhí)行總是離不開線程池的調(diào)度调俘。...
    編走編想閱讀 5,256評論 4 10
  • 下面是我自己收集整理的Java線程相關(guān)的面試題,可以用它來好好準(zhǔn)備面試旺垒。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 14,738評論 14 507
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里彩库,通過寫-讀內(nèi)存中的公共狀態(tài)進(jìn)行隱...
    澤毛閱讀 4,341評論 2 22
  • 如果情人節(jié)當(dāng)初被翻譯成愛人節(jié),恐怕如今的輿論就不會像這樣的曖昧先蒋。 現(xiàn)在有種現(xiàn)象骇钦,人們都覺得男人有了情人并不是件不恥...
    千尋凌霄閱讀 331評論 0 2