Skip to content

Latest commit

 

History

History
174 lines (133 loc) · 4.37 KB

findException.md

File metadata and controls

174 lines (133 loc) · 4.37 KB

在 ThreadPool 中尋找被藏起來的 Exception




使用 ThreadPool 或許是一件好事,但是他還是有坑的,ThreadPool 有可能會吃掉本來該被拋出的 Exception,導致我們無法 debug。

來看一下被吃掉 Exception 的範例:


public class DivTask implements Runnable {

    private int a, b;

    public DivTask(int a, int b){
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        double re = a/b;
        System.out.println(re);
    }

    public static void main(String[] args) {
        ExecutorService es = new ThreadPoolExecutor(
                0,
                Integer.MAX_VALUE,
                0L,
                TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>()
                );
        for (int i= 0; i < 5; ++i){
            es.submit(new DivTask(100, i));
        }
    }
}

這個程式計算 100 除以 0 ~ 4 的結果,我們預計會得到 5 個結果,但事實上只有 4 個:


100.0
25.0
33.0
50.0

發生了甚麼問題呢?是因為 100 / 0 導致的,如果在一般的程式中算一下 100 / 0 會報出以下錯誤:


java.lang.ArithmeticException / by zero

如果有這個鎖誤訊息我們 debug 會快很多,但是在這個範例中,錯誤訊息被 "消失" 了。這就是 ThreadPool 的坑。要想解決這一問題,我們要自己動手繼承 ThreadPoolExecutor 來擴展功能。


public class TraceThreadPoolExecutor extends ThreadPoolExecutor {

    public TraceThreadPoolExecutor(int corePoolSize,
                                   int maximumPoolSize,
                                   long keepAliveTime,
                                   TimeUnit unit,
                                   BlockingQueue<Runnable> workQueue
                                   ) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public void execute(Runnable task) {
        super.execute(wrap(task, clientTrace(), Thread.currentThread().getName()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
    }

    private Exception clientTrace() {
        return new Exception("client stack trace.");
    }

    private Runnable wrap(final Runnable task, final Exception clientStack, String threadName){
        return () -> {
          try{
              task.run();
          } catch (Exception e){
              clientStack.printStackTrace();
              throw  e;
          }
        };
    }


    public static void main(String[] args) {
        ExecutorService es = new TraceThreadPoolExecutor(
                0,
                Integer.MAX_VALUE,
                0L,
                TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>()
        );
        for (int i= 0; i < 5; ++i){
            es.execute(new DivTask(100, i)); // #1
        }
    }
}

注意 #1 處改用 execute() 方法,因為這樣才能印出具體的錯誤訊息,使用 submit() 只能得到部份錯誤訊息。


印出結果:


java.lang.Exception: client stack trace.
	at mysticism.threadPool.TraceThreadPoolExecutor.clientTrace(TraceThreadPoolExecutor.java:27)
	at mysticism.threadPool.TraceThreadPoolExecutor.execute(TraceThreadPoolExecutor.java:18)
	at mysticism.threadPool.TraceThreadPoolExecutor.main(TraceThreadPoolExecutor.java:51)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at mysticism.threadPool.DivTask.run(DivTask.java:19)
	at mysticism.threadPool.TraceThreadPoolExecutor.lambda$wrap$0(TraceThreadPoolExecutor.java:33)
	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)
100.0
25.0
33.0
50.0




2022/08/10 補充


在做專案中發現,其實繼承了 ThreadPoolExecutor,只要實作 afterExecute() 方法就可以達到上述效果。

@Override
protected void afterExecute(Runnable r, Throwable t) {
    log.info("Task finished, TASK: " + r.getClass().getName());
     if (t!= null){
         log.error("Task failed, TASK:" + ((BaseScheduleTask) r).getName(), t);
     }
}