之前使用線程執(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#dispatchUncaughtExceptionprivate 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方法后才能捕獲到
附往期文章:歡迎你的閱讀、點贊坡垫、評論
并發(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存儲金額為什么要扣我工資