Java線程池中的異常處理
§ 前置知識
線程池中的任務有兩種,一種有返回值,一種無返回值。通常對應着兩種提交任務的方法:
submit方法:雖然參數是Runnable,但由於返回值爲Future,所以通常傳入的參數爲FutureTask類的對象。(FutureTask間接實現了Runnable接口和Future接口)
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; }
execute方法:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); 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類中的Worker類,而任務的執行都是通過
runWorker()
方法,這裏截取關鍵部分:try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); // 調用對應任務的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(); }
§ 默認情況存在的問題
由前置知識可知,
runWorker()
方法內,若task.run()
出現異常,會拋出,然後進入afterExecute(task, thrown)
方法,而該方法默認爲空,所以默認情況下我們無法得到想要的異常信息。針對submit和execute提交的有返回值和無返回值的任務,有兩種解決方向如下。
§ submit提交的有返回值的任務
由於任務是FutureTask類的,所以在執行
task.run()
時,執行的是FutureTask中重寫的run()方法,截取關鍵部分如下:try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } ////// protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; // !!!!關鍵 UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }
***可見,任務執行出現異常後,不拋出,而是存入返回值當中。因此,若要獲取異常信息則需通過返回值獲取,所以可以在try catch中通過FutureTask的get()方法獲取返回結果(異常):***
try{ future.get(); }catch(xxxException){ // do sth }
§ 通過execute提交的無返回值的任務
【法1】
從前面所講可以知道,無返回值的任務,即原生Runnable,在執行
task.run()
時若有異常則會拋出,並只能在afterExecute(task, thrown)
中進行自定義的處理,***所以可以自定義線程池,繼承ThreadPoolExecutor並複寫其afterExecute(Runnable r, Throwable t)方法。***
【法2】
***實現
Thread.UncaughtExceptionHandler
接口,並重寫實現void uncaughtException(Thread t, Throwable e);
方法,在該方法中處理異常。並將該handler傳遞給線程池的ThreadFactory。***具體可參考:https://juejin.im/post/5d27c3e6518825451f65ee15