為了方便遍描述問題,如下是簡化后的
public class RunException {public static void main(String[] args) { ExecutorService readerPool = Executors.newFixedThreadPool(3); readerPool.submit(new Runnable() { public void run() { throw new RuntimeException("異常"); } }); readerPool.shutdown(); } }
此處FixedThreadPool吞掉了異常。
問題
- 為什么不能拋出到外部線程捕獲
- submit為什么不能打印報錯信息
- execute怎么使用logger打印報錯信息
為什么不能拋出到外部線程捕獲
jvm會在線程即將死掉的時候捕獲所有未捕獲的異常進行處理。默認使用的是Thread.defaultUncaughtExceptionHandler
submit為什么不能打印報錯信息
public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null);//創建FutureTask類 execute(ftask); return ftask; }
查看FutureTask.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 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(ex);
,將線程狀態由completing改為exceptional,并將異常信息存在outcome
中:
//這個方法就是這事線程狀態為completing -> exceptional//同時用outcome保存異常信息。protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }
繼續查看outcome的使用:
//report會拋出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會調用report()方法 public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); }
- report會拋出exception信息,但report是私有方法;
- get會調用report()方法
所以如果需要獲取異常信息就需要調用get()
方法。
execute怎么輸入logger日志
查看execute的實現ThreadPoolExecutor.execute()
:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
從代碼可知,線程池將任務加入了任務隊列,需要看看線程在哪執行任務的。那么只需要看看有沒有獲取任務的函數,ThreadPoolExecutor.getTask()
即是獲取任務的函數,通過查找,ThreadPoolExecutor.runWorker
調用了ThreadPoolExecutor.getTask()
,它應該是執行任務的代碼:
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) { //這里直接拋出所有Runtime異常 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); } }
代碼注釋中看到獲取RuntimeException
的位置了。
這里拋出的異常在哪里處理呢? 接下來處理是交由jvm處理,從已經學習的知識中只知道jvm調用Thread.dispatchUncaughtException
來處理所有未捕獲的異常
/*** 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); }
這里可以根據該方法注釋解釋,意思就是這個方法只用于JVM調用,處理線程未捕獲的異常。 繼續查看getUncaughtExceptionHandler()
方法:
public interface UncaughtExceptionHandler {s void uncaughtException(Thread t, Throwable e); } // 處理類 private volatile UncaughtExceptionHandler uncaughtExceptionHandler; // 默認處理類 private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; /** * 設置默認的處理類,注意是靜態方法,作用域為所有線程設置默認的處理類 **/ public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission( new RuntimePermission("setDefaultUncaughtExceptionHandler") ); } defaultUncaughtExceptionHandler = eh; } //獲取默認處理類 public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){ return defaultUncaughtExceptionHandler; } //獲取處理類,注意不是靜態方法,只作用域該線程 //處理類為空使用ThreadGroup public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; } //設置處理類 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) { //獲取處理類型進行異常處理 getUncaughtExceptionHandler(www.mumingyue.cn).uncaughtException(this, e); }
如果線程UncaughtExceptionHandler
處理器為空則threadGroup
處理器 查看threadGroup
:
public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t,www.douniu2.cc 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); } } }
從代碼中可以看出,
- 如果父進程不為空,則使用父進程處理未捕獲異常;
- 如果無父進程,則獲取默認的
UncaughtExceptionHandler
進行處理。- 默認的
UncaughtExceptionHandler
為null,則使用Sytem.err
將錯誤信息輸出; - 默認的
UncaughtExceptionHandler
不為null,則使用UncaughtExceptionHandler
進行處理。
- 默認的
所以有兩個方法實現用logger輸出:
- Thread定義
uncaughtExceptionHandler
:Thread.setUncaughtExceptionHandler(www.tianscpt.com)
,該方法僅能設置某個線程的默認UncaughtExceptionHandler
。 - Thread定義
defaultUncaughtExceptionHandler
:使用Thread.setDefaultUncaughtExceptionHandler
,該方法設置所有線程的默認UncaughtExceptionHandler
。
測試程序
僅某個線程設置默認UncaughtExceptionHandler
public static void oneThreadUncaughtExceptionHandler() { Thread t1 = new Thread((www.mhylpt.com/) -> { throw new RuntimeException(" t1 runtime exception"); }, "t1"); t1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(Thread.currentThread(www.baihuiyulep.cn) + "trigger uncaugh exception handler"); } }); t1.start(); Thread t2 = new Thread(() -> { throw new RuntimeException(" t2 runtime exception"); }, "t2"); t2.start(); }
設置defaultUncaughtExceptionHandler
public static void defaultThreadUncaughtExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(www.ysyl157.com Thread t, Throwable e) { System.out.println(Thread.currentThread() + "trigger uncaugh exception handler"); } }); new Thread(() -> { throw new RuntimeException(www.tianjiuyule178.com??" t1 runtime exception"); }, "t1").start(); new Thread(() -> { throw new RuntimeException(" t2 runtime exception"); }, "t2").start(); }
解惑
那為什么我們的例子代碼中,異常不會輸出呢?應該有兜底的
System.err
來輸出異常才對。 不是這樣的,我們的例子中的異常實際上是處理了的,它捕獲了異常,并且保存到了outcome中。僅僅有未捕獲的異常,JVM才會調用Thread.dispatchUncaughtException
來處理。