自己的事情自己做盏筐,線程異常處理

之前使用線程執(zhí)行任務(wù)的時候姓建,總是忽略了線程異常的處理惠昔,直到最近看書

線程出現(xiàn)異常測試類

  • 任務(wù)類:Task.java

    public class Task implements Runnable {
        private int i;
    
        public Task(int i) {
            this.i = i;
        }
    
        @Override
        public void run() {
            if (i == 5) {
                //System.out.println("throw exception");
                throw new IllegalArgumentException();
            }
            System.out.println(i);
        }
    }
    

    如果i==5,將拋出一個異常

  • 線程測試類:TaskTest.java

    public class TestTask {
        public static void main(String[] args) {
            int i = 0;
            while (true) {
                if (i == 10) break;
                try {
                    new Thread(new Task(i++)).start();
                } catch (Exception e) {
                    System.out.println("catch exception...");
                }
            }
        }
    }
    

    通過使用try-catch豹绪,嘗試對拋出的異常進行捕獲

  • 測試結(jié)果

    Connected to the target VM, address: '127.0.0.1:64551', transport: 'socket'
    0
    1
    2
    3
    4
    6
    7
    8
    9
    Exception in thread "pool-1-thread-1" java.lang.IllegalArgumentException
        at com.h2t.study.thread.Task.run(Task.java:21)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
    

    異常沒有被捕獲价淌,只是在控制臺打印了異常,并且不影響后續(xù)任務(wù)的執(zhí)行
    emmmm這是為什么呢瞒津,捕獲不到異常就不知道程序出錯了蝉衣,到時候哪天有個任務(wù)不正常排查都排查不到,這樣是要不得的巷蚪〔≌保看一下Thread這個類,有個叫dispatchUncaughtException的方法屁柏,作用如其名啦膜,分發(fā)未捕獲的異常,把這段代碼揪出來:Thread#dispatchUncaughtException

    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    

    find usage是找不到該方法在哪里調(diào)用的前联,因為這個方法只被JVM調(diào)用
    Thread#getUncaughtExceptionHandler:獲取UncaughtExceptionHandler接口實現(xiàn)類

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
    

UncaughtExceptionHandler是Thread中定義的接口功戚,在Thread類中uncaughtExceptionHandler默認是null,因此該方法將返回group似嗤,即實現(xiàn)了UncaughtExceptionHandler接口的ThreadGroup類
UncaughtExceptionHandler#uncaughtException:ThreadGroup類的uncaughtException方法實現(xiàn)

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}

因為在Thread類中沒有對group【parent】和defaultUncaughtExceptionHandler【Thread.getDefaultUncaughtExceptionHandler】進行賦值啸臀,因此將進入最后一層條件,將異常打印到控制臺中烁落,對異常不做任何處理乘粒。
整個異常處理器調(diào)用鏈如下:


首先判斷默認異常處理器【defaultUncaughtExceptionHandler】是不是為null,在判斷線程組異常處理器【group】是不是為null伤塌,在判斷自定義異常處理器【uncaughtExceptionHandler】是不是為null灯萍,都為null則在控制臺打印異常

線程異常處理

分析了一下源碼就知道如果想對任務(wù)執(zhí)行過程中的異常進行處理一個就是讓ThreadGroup不為null,另外一種思路就是讓UncaughtExceptionHandler類型的變量值不為null每聪。

  • 異常處理器:ExceptionHandler.java

    private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("異常捕獲到了:" + e);
        }
    }
    
  • 設(shè)置默認異常處理器

    Thread.setDefaultUncaughtExceptionHandler((t, e) -> System.out.println("異常捕獲到  了: " + e));
    int i = 0;
    while (true) {
      if (i == 10) break;
      Thread thread = new Thread(new Task(i++));
      thread.start();
    }
    

    打印結(jié)果:

    0
    2
    1
    3
    9
    6
    7
    4
    異常捕獲到了:java.lang.IllegalArgumentException
    8
    

    通過設(shè)置默認異常就不需要為每個線程都設(shè)置一次了

  • 設(shè)置自定義異常處理器

    Thread t = new Thread(new Task(i++));
    t.setUncaughtExceptionHandler(new ExceptionHandler());
    

    打印結(jié)果:

    0
    2
    4
    異常捕獲到了:java.lang.IllegalArgumentException
    6
    1
    3
    7
    9
    8
    
  • 設(shè)置線程組異常處理器

    MyThreadGroup myThreadGroup = new MyThreadGroup("測試線程線程組");
    Thread t = new Thread(myThreadGroup, new Task(i++))
    

    自定義線程組:MyThreadGroup.java

    private static class MyThreadGroup extends ThreadGroup {
        public MyThreadGroup(String name) {
            super(name);
        }
    
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("捕獲到異常了:" + e);
        }
    }
    

    打印結(jié)果:

    1
    2
    0
    4
    3
    6
    7
    8
    9
    捕獲到異常了:java.lang.IllegalArgumentException
    

線程組異常捕獲處理器很適合為線程進行分組處理的場景旦棉,每個分組出現(xiàn)異常的處理方式不相同
設(shè)置完異常處理器后異常都能被捕獲了齿风,但是不知道為什么設(shè)置異常處理器后任務(wù)的執(zhí)行順序亂了,難道是因為為每個線程設(shè)置異常處理器的時間不同【想不通】

線程池異常處理

一般應(yīng)用中線程都是通過線程池創(chuàng)建復(fù)用的绑洛,因此對線程池的異常處理就是為線程池工廠類【ThreadFactory實現(xiàn)類】生成的線程添加異常處理器

  • 默認異常處理器

     Thread.setDefaultUncaughtExceptionHandler(new  ExceptionHandler());
     ExecutorService es = Executors.newCachedThreadPool();
    es.execute(new Task(i++))
    
  • 自定義異常處理器

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>());
    threadPoolExecutor.setThreadFactory(new MyThreadFactory());
    threadPoolExecutor.execute(new Task(i++));
    

    自定義工廠類:MyThreadFactory.java

    private static class MyThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
          Thread t = new Thread();
          //自定義UncaughtExceptionHandler
          t.setUncaughtExceptionHandler(new ExceptionHandler());
          return t;
       }
    }
    

設(shè)計原則救斑,為什么要由線程自身進行捕獲

來自JVM的設(shè)計理念"線程是獨立執(zhí)行的代碼片斷,線程的問題應(yīng)該由線程自己來解決真屯,而不要委托到外部"脸候。因此在Java中,線程方法的異嘲竽瑁【即任務(wù)拋出的異吃寺伲】,應(yīng)該在線程代碼邊界之內(nèi)處理掉配深,而不應(yīng)該在線程方法外面由其他線程處理

線程執(zhí)行Callable任務(wù)

前面介紹的是線程執(zhí)行Runnable類型任務(wù)的情況携添,眾所周知,還有一種有返回值的Callable任務(wù)類型
測試代碼:TestTask.java

public class TestTask {
    public static void main(String[] args) {
        int i = 0;
        while (true) {
            if (i == 10) break;
            FutureTask<Integer> task = new FutureTask<>(new CallableTask(i++));
            Thread thread = new Thread(task);
            thread.setUncaughtExceptionHandler(new ExceptionHandler());
            thread.start();
        }
    }
    
    private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("異常捕獲到了:" + e);
        }
    }
}

打印結(jié)果:

Disconnected from the target VM, address: '127.0.0.1:64936', transport: 'socket'
0
1
2
3
4
6
7
8
9

觀察結(jié)果凉馆,異常沒有被捕獲薪寓,thread.setUncaughtExceptionHandler(new ExceptionHandler())方法設(shè)置無效,emmmmm,這又是為什么呢,在問為什么就是十萬個為什么兒童了伪窖。查看FutureTask的run方法,F(xiàn)utureTask#run:

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

FutureTask#setException

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
      //將異常設(shè)置給outcome變量
      outcome = t;
      //設(shè)置任務(wù)的狀態(tài)為EXCEPTIONAL
      UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
      finishCompletion();
    }
}

看到catch這段代碼母谎,當執(zhí)行任務(wù)捕獲到異常的時候,會將任務(wù)的處理結(jié)果設(shè)置為null京革,并且調(diào)用setException方法對捕獲的異常進行處理奇唤,因為setUncaughtExceptionHandler只對未捕獲的異常進行處理,F(xiàn)utureTask已經(jīng)對異常進行了捕獲處理匹摇,因此調(diào)用setUncaughtExceptionHandler捕獲異常無效
對任務(wù)的執(zhí)行結(jié)果調(diào)用get方法:

int i = 0;
while (true) {
    if (i == 10) break;
    FutureTask<Integer> task = new FutureTask<>(new CallableTask(i++));
    Thread thread = new Thread(task);
    thread.setUncaughtExceptionHandler(new ExceptionHandler());
    thread.start();
    //打印結(jié)果
    try {
    System.out.println(task.get());
    } catch (Exception e) {
      System.out.println("異常被抓住了, e: " + e);
    }
}

執(zhí)行結(jié)果將會將捕獲到的異常打印出來咬扇,執(zhí)行結(jié)果:

0
1
2
3
4
異常被抓住了, e: java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException
6
7
Disconnected from the target VM, address: '127.0.0.1:50900', transport: 'socket'
8
9

FutureTask#get

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
      //未完成等待任務(wù)執(zhí)行完成
      s = awaitDone(false, 0L);
    return report(s);
}

FutureTask#report

private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
      return (V)x;
    if (s >= CANCELLED)
      throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

outcome在setException方法中被設(shè)置為了異常,并且s為state的狀態(tài)最終被設(shè)置為EXCEPTIONAL廊勃,因此方法將捕獲的任務(wù)拋出【new ExecutionException((Throwable)x)】
總結(jié):
Callable任務(wù)拋出的異常能在代碼中通過try-catch捕獲到懈贺,但是只有調(diào)用get方法后才能捕獲到

image.png

附往期文章:歡迎你的閱讀、點贊坡垫、評論

并發(fā)相關(guān)
1.為什么阿里巴巴要禁用Executors創(chuàng)建線程池梭灿?

設(shè)計模式相關(guān):
1. 單例模式,你真的寫對了嗎冰悠?
2. (策略模式+工廠模式+map)套餐 Kill 項目中的switch case

JAVA8相關(guān):
1. 使用Stream API優(yōu)化代碼
2. 親堡妒,建議你使用LocalDateTime而不是Date哦

數(shù)據(jù)庫相關(guān):
1. mysql數(shù)據(jù)庫時間類型datetime、bigint溉卓、timestamp的查詢效率比較
2. 很高興皮迟!終于踩到了慢查詢的坑

高效相關(guān):
1. 擼一個Java腳手架搬泥,一統(tǒng)團隊項目結(jié)構(gòu)風格

日志相關(guān):
1. 日志框架,選擇Logback Or Log4j2万栅?
2. Logback配置文件這么寫佑钾,TPS提高10倍

工程相關(guān):
1. 閑來無事西疤,動手寫一個LRU本地緩存
2. Redis實現(xiàn)點贊功能模塊
3. JMX可視化監(jiān)控線程池
4. 權(quán)限管理 【SpringSecurity篇】
5. Spring自定義注解從入門到精通
6. java模擬登陸優(yōu)酷
7. QPS這么高烦粒,那就來寫個多級緩存吧
8. java使用phantomjs進行截圖

其他:
1. 使用try-with-resources優(yōu)雅關(guān)閉資源
2. 老板,用float存儲金額為什么要扣我工資

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末代赁,一起剝皮案震驚了整個濱河市扰她,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芭碍,老刑警劉巖徒役,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窖壕,居然都是意外死亡忧勿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門瞻讽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸳吸,“玉大人,你說我怎么就攤上這事速勇∩卫” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵烦磁,是天一觀的道長养匈。 經(jīng)常有香客問我,道長都伪,這世上最難降的妖魔是什么呕乎? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮陨晶,結(jié)果婚禮上猬仁,老公的妹妹穿的比我還像新娘。我一直安慰自己珍逸,他們只是感情好逐虚,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谆膳,像睡著了一般叭爱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漱病,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天买雾,我揣著相機與錄音把曼,去河邊找鬼。 笑死漓穿,一個胖子當著我的面吹牛嗤军,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晃危,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叙赚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了僚饭?” 一聲冷哼從身側(cè)響起震叮,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳍鸵,沒想到半個月后苇瓣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡偿乖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年击罪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贪薪。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡媳禁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出古掏,到底是詐尸還是另有隱情损话,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布槽唾,位于F島的核電站丧枪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏庞萍。R本人自食惡果不足惜拧烦,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钝计。 院中可真熱鬧恋博,春花似錦、人聲如沸私恬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽本鸣。三九已至疫衩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荣德,已是汗流浹背闷煤。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工童芹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲤拿。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓假褪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親近顷。 傳聞我的和親對象是個殘疾皇子生音,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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

  • 一、前言 線程池技術(shù)是服務(wù)器端開發(fā)中常用的技術(shù)幕庐。不論是直接還是間接久锥,各種服務(wù)器端功能的執(zhí)行總是離不開線程池的調(diào)度。...
    幽瀾先生閱讀 875評論 0 0
  • 一异剥、wait--notify--sleep Object obj = new Object(); obj.wait...
    fe0180bd6eaf閱讀 337評論 0 1
  • 執(zhí)行多線程并發(fā)任務(wù)的時候,如果任務(wù)類型相同絮重,一般會考慮使用線程池冤寿,一方面利用了并發(fā)的優(yōu)勢,一方面避免創(chuàng)建大量線程得...
    德彪閱讀 23,466評論 2 19
  • 參考自編寫高質(zhì)量代碼++改善Java程序的151個建議 提取碼:ylv4 1. 不推薦在線程中重寫start方法青伤;...
    小胖學編程閱讀 690評論 0 4
  • 記得有一句歌詞 每天陪我走路的只有影子 生活在孤獨寂寞中 人是最善思考的 當我們隨著時間的流失 隨著年齡的增長 可...
    闐溢閱讀 201評論 0 0