前言
在閱讀線程相關(guān)知識(shí)的時(shí)候發(fā)現(xiàn)線程吞掉異常的知識(shí),故記錄下來(lái)。
代碼示例
ExecutorService executorService = Executors.newFixedThreadPool(1);
try {
executorService.submit(() -> {
Object obj = null;
System.out.println(obj.toString());
});
} catch (Exception e) {
System.out.println("catch Exception");
e.printStackTrace();
}
try {
executorService.execute(() -> {
Object obj = null;
System.out.println(obj.toString());
});
} catch (Exception e) {
System.out.println("catch Exception");
e.printStackTrace();
}
運(yùn)行結(jié)果:
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
at my.jdk.concurrent.ThreadPoolTest.lambda$uncaughtExceptionTest$2(ThreadPoolTest.java:68)
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)
catch肯定不會(huì)獲取到信息,同時(shí)submit報(bào)錯(cuò)信息也沒(méi)有打印出來(lái),只打印的execute報(bào)錯(cuò)信息
問(wèn)題
- 為什么不能拋出到外部線程捕獲
- submit為什么不能打印報(bào)錯(cuò)信息
- execute怎么使用logger打印報(bào)錯(cuò)新信
原因
為什么不能拋出到外部線程捕獲?
jvm會(huì)在線程即將死掉的時(shí)候捕獲所有未捕獲的異常進(jìn)行處理。
submit為什么不能打印報(bào)錯(cuò)信息
submit源碼實(shí)現(xiàn):
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);//創(chuàng)建FutureTask類(lèi)
execute(ftask);
return ftask;
}
查看FutureTask.run方法實(shí)現(xiàn)
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) {
//這里捕獲了所有異常調(diào)用setException
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);
}
}
接著查看setException實(shí)現(xiàn)
//這個(gè)方法就是這事線程狀態(tài)為completing -> exceptional
//同時(shí)用outcome保存異常信息。
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
然后我們繼續(xù)追蹤outcome的使用
//report會(huì)拋出exception信息
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);
}
//get會(huì)調(diào)用report方法
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
所有知道了如果想獲取sumbit的異常信息需要調(diào)用get方法,如下:
@Test
public void caughtSumbitException() {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future future = executorService.submit(() -> {
Object obj = null;
System.out.println(obj.toString());
});
try {
future.get();
} catch (Exception e) {
System.out.println("catch NullPointException");
}
}
輸出:
catch NullPointException
execute怎么輸入logger日志
查看execute代碼實(shí)現(xiàn)
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
//這里直接拋出所有異常
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
這里拋出的異常在哪里處理呢?
接下來(lái)處理是交由jvm處理,由于本人對(duì)jvm源碼不了解,只知道jvm調(diào)用Thread. dispatchUncaughtException來(lái)處理所有未捕獲的異常
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
這里可以根據(jù)該方法注釋解釋?zhuān)馑季褪沁@個(gè)方法只用于JVM調(diào)用,處理線程未捕獲的異常。
繼續(xù)查看getUncaughtExceptionHandler()方法
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
// 處理類(lèi)
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// 默認(rèn)處理類(lèi)
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
/**
* 設(shè)置默認(rèn)的處理類(lèi),注意是靜態(tài)方法,作用域?yàn)樗芯€程設(shè)置默認(rèn)的處理類(lèi)
**/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}
//獲取默認(rèn)處理類(lèi)
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
//獲取處理類(lèi),注意不是靜態(tài)方法,只作用域該線程
//處理類(lèi)為空使用ThreadGroup
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
//設(shè)置處理類(lèi)
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
//獲取處理類(lèi)型進(jìn)行異常處理
getUncaughtExceptionHandler().uncaughtException(this, e);
}
如果線程處理器為空則threadGroup處理器
查看threadGroup
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);
}
}
}
獲取默認(rèn)處理器進(jìn)行線程處理
默認(rèn)處理器為空則在Sytem.err就行錯(cuò)誤信息輸出
到這里有兩個(gè)方法可以實(shí)現(xiàn)用logger輸出
- thread定義uncaughtExceptionHandler
- Thread定義defaultUncaughtExceptionHandler
示例代碼:
@Test
public void setUncaughtExceptionHandler() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t1");
t1.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
t1.start();
new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t2").start();
Thread.sleep(1000);
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler defaultHandler;
public MyUncaughtExceptionHandler() {
this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.info("logger println exception info, threadName:{}", t.getName());
}
}
輸出:
Exception in thread "t2" java.lang.RuntimeException: soming Exception
at my.jdk.concurrent.ThreadTest.lambda$setUncaughtExceptionHandler$1(ThreadTest.java:27)
at java.lang.Thread.run(Thread.java:745)
[no tracer] 2018-06-05 19:01:55.230 [t1] INFO my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t1
可以看到setUncaughtExceptionHandler只作用與t1,t2還是system.err輸出
@Test
public void defaultUncaughtExceptionHandler() throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t1").start();
new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t2").start();
Thread.sleep(1000);
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler defaultHandler;
public MyUncaughtExceptionHandler() {
this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.info("logger println exception info, threadName:{}", t.getName());
}
}
輸出:
[no tracer] 2018-06-05 19:03:37.292 [t2] INFO my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t2
[no tracer] 2018-06-05 19:03:37.292 [t1] INFO my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t1
作用域?yàn)樗芯€程