1. 程式人生 > >java執行緒池中任務異常處理

java執行緒池中任務異常處理

首先我們看個例子,當使用執行緒池執行任務時如果某個任務出現異常會是什麼效果

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DivTask implements Runnable {
    int a,b;
    @Override
    public void run() {
        System.out.println(a/b);
    }
    public
DivTask(int a, int b) { super(); this.a = a; this.b = b; } public static void main(String[] args) { ThreadPoolExecutor pools=new ThreadPoolExecutor(0, 10, 0,TimeUnit.SECONDS,new SynchronousQueue<Runnable>()); for(int i=0;i<5;i++){ pools.submit(new
DivTask(100,i)); } } } 執行結果 100 50 25

你會發現少了一個結果輸出,這個缺失的原因是由於i=0導致的,但是系統卻一點錯誤提示都沒有,後面如果使用在複雜的業務場景中,那麼出現這種錯誤去排查起來可就困難了

解決辦法
1. 將sumit()方法換成execute()執行,即pools.execute(new DivTask(100,i));而且一個任務的異常對其他任務的執行不會造成任何影響
2. 使用改造submit(),使用future.get()獲取異常資訊

Future future=pools.submit(new DivTask(100
,i)); future.get(); 執行結果如下 Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.util.concurrent.FutureTask.get(FutureTask.java:188) at threadpool.DivTask.main(DivTask.java:26) Caused by: java.lang.ArithmeticException: / by zero at threadpool.DivTask.run(DivTask.java:15) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)

我們發現異常資訊雖能正常打印出來,可仔細看,卻發現異常卻發現只知道異常是從哪裡丟擲,不知道這個產生異常的任務是從哪裡提交的,任務的具體提交位置已經被執行緒池完全淹沒了,我們只能看到執行緒池的排程流程,這對於我們排查錯誤造成很大的困難。

如何詳細的打印出執行緒池中提交的任務所丟擲的異常資訊
1. 在提交的任務中將異常捕獲,不拋給執行緒池

//其實就是將所有的業務邏輯都使用try...catch起來,但是這種辦法感覺很low,程式碼量增加不少,不推薦
@Override
    public void run() {
        try {
            //處理所有的業務邏輯
        } catch (Throwable e) {
            //列印日誌等
        } finally {
            //其他處理
        }
    }

2.自定義執行緒池,execute()、submit()方法,在呼叫這些方法前,對任務進行一次包裝然後再提交,實際上也是第一種方法的思路,只是不用在每個run()都去try catch處理異常

public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadPoolExecutor pools=new ThreadPoolExecutor(0, 10, 0,TimeUnit.SECONDS,new SynchronousQueue<Runnable>()){
            private Runnable warp(final Runnable task,final Exception clientStack, String clientThreadName){
                return new Runnable() {
                    @Override
                    public void run() {
                        try {
                            task.run();
                        } catch (Exception e) {
                            clientStack.printStackTrace();
                            throw e;
                        }
                    }
                };
            }
            private Exception clientTrace(){
                return new Exception("client stack trace");
            }
            @Override
            public void execute(Runnable command) {
                // TODO Auto-generated method stub
                super.execute(warp(command,clientTrace(), Thread.currentThread().getName()));
            }
            @Override
            public Future<?> submit(Runnable task) {
                return super.submit(warp(task,clientTrace(),Thread.currentThread().getName()));
            }
        };
        for(int i=0;i<5;i++){
            pools.execute(new DivTask(100,i));
        }
    }

執行結果

100
33
25
50
java.lang.Exception: client stack trace
    at threadpool.DivTask$1.clientTrace(DivTask.java:39)
    at threadpool.DivTask$1.execute(DivTask.java:44)
    at threadpool.DivTask.main(DivTask.java:52)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at threadpool.DivTask.run(DivTask.java:16)
    at threadpool.DivTask$1$1.run(DivTask.java:30)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

為什麼現在能定位出異常任務是在哪裡提交的,關鍵點就在於我們在submit()中傳入了一個自定義的Exception。一旦任務有異常,我們就會將此異常丟擲,這樣我們就能定位是哪裡submit的了.