首先我们看个例子,当使用线程池执行任务时如果某个任务出现异常会是什么效果
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的了.