ThreadPoolExecutor(七)——总结补充
?1.關于異常
線程池在執行execute方法和submit任務的時候,如果任務中拋出了異常,外部線程無法感知,所以要做一些措施來進行處理,下面就說一下產生這種效果的原因以及一些可用的處理方法。
關于異常的處理內容參考了文章并進行了相關補充:ExecutorService-10個要訣和技巧
1.execute執行的任務拋出異常
1.原理
測試代碼:
public static void main(String[] args) {Executors.newFixedThreadPool(1);try {Executors.newFixedThreadPool(1).execute(new Runnable() {@Override public void run() {int i=1/0;}});} catch (Exception e) {System.out.println("catch!!!!");} finally {System.out.println("normal");}}這個異常會被內部吞掉的,不會被catch塊catch到。
但是程序會用System.err和e.printStackTrace方式打印異常棧信息,是由下面這個方法觸發的:
看注釋,這個方法是用來分發處理一個uncaught的異常交由UncaughtExceptionHandler來處理,是由JVM底層來調用的。
具體的實現在ThreadGroup類中,有個uncaughtException方法,
/*** Called by the Java Virtual Machine when a thread in this* thread group stops because of an uncaught exception, and the thread* does not have a specific {@link Thread.UncaughtExceptionHandler}* installed.* <p>* Applications can override this method in subclasses of* <code>ThreadGroup</code> to provide alternative handling of* uncaught exceptions.** @param t the thread that is about to exit.* @param e the uncaught exception.* @since JDK1.0*/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);}}}先看注釋,該方法是由JVM調用,當一個線程由于一個uncaught異常而導致線程終止,而且該線程沒有指定一個UncaughtExceptionHandler。
在else if塊中會用System.err和e.printStackTrace配合打印異常信息。
這種情況為了保證我們的程序在運行時能夠感知異常,我們可以在我們的task的run方法中用try catch把語句包起來,然后在catch記錄日志和監控。注意這里既是把線程包裝一下重新跑出去也是沒用的。
2.解決方案
自己把線程封裝一下
/*** 第一種實現自己定義線程,同時自己定義一個ThreadFactory的實現呢*/static class MyAppThread extends Thread {public static final String DEFAULT_NAME = "MyAppThread";private static final AtomicInteger created = new AtomicInteger();private static final AtomicInteger alive = new AtomicInteger();public MyAppThread(Runnable r) {this(r, DEFAULT_NAME);}public MyAppThread(Runnable runnable, String name) {super(runnable, name + "-" + created.incrementAndGet());this.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {public void uncaughtException(Thread t,Throwable e) {logger.error("UNCAUGHT1 in thread " + t.getName(), e);}});}public void run() {System.out.println("Created " + getName());try {alive.incrementAndGet();super.run();} finally {alive.decrementAndGet();System.out.println("Exiting " + getName());}}public static int getThreadsCreated() {return created.get();}public static int getThreadsAlive() {return alive.get();}}自己封裝線程的工廠類
public class MyThreadFactory2 {public Thread newThread(Runnable runnable) {Thread ret = Executors.defaultThreadFactory().newThread(runnable);ret.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {public void uncaughtException(Thread t,Throwable e) {logger.error("UNCAUGHT2 in thread " + t.getName(), e);}});return ret;}}在構造線程池的時候,作為參數傳遞我們自己封裝的線程工廠。
2.submit方法提交的任務中拋出異常
/*** @throws RejectedExecutionException {@inheritDoc}* @throws NullPointerException {@inheritDoc}*/public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();RunnableFuture<T> ftask = newTaskFor(task);execute(ftask);return ftask;}線程池的submit方法提交的任務是一個FutureTask,它既含有Runnable的任務屬性也含有Future的屬性。FutureTask的run方法中調用的是內部實現的AbstractQueuedSynchronizer的innerRun方法,
void innerRun() {if (!compareAndSetState(READY, RUNNING))return;runner = Thread.currentThread();if (getState() == RUNNING) { // recheck after setting threadV result;try {result = callable.call();} catch (Throwable ex) {setException(ex);return;}set(result);} else {releaseShared(0); // cancel}}所以call方法拋出異常的話,會被catch捕獲并用setException進行異常的設置。在設置異常之后,用future的get(內部是Sync的innerGet方法)方法獲取結果的時候會拋出異常,
V innerGet() throws InterruptedException, ExecutionException {acquireSharedInterruptibly(0);if (getState() == CANCELLED)throw new CancellationException();if (exception != null)throw new ExecutionException(exception);return result;}所以為了確保外部感知到這個異常,有兩個方法:
1.一定要是用future的阻塞的get方法來獲取結果。如果調用帶超時時間的get方法,如果異常在超時之后發生的話,外部也無法感知到這個異常。如果業務上在超時之后已經不關心這個異常了的話,也可以只對超時進行監控和日志輸出。
2.對call方法內部進行try catch包裹,在有異常發生的時候,設置一個特殊值來表示異常發生情況下的返回,同時記錄日志和監控。
?
總結
以上是生活随笔為你收集整理的ThreadPoolExecutor(七)——总结补充的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抽象类和接口的小程序
- 下一篇: Tableau可视化技巧-让你的图表跟随