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());
        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()){
            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的了.

你可能感兴趣的:(java并发编程,线程池,java,异常处理)