线程池的异常堆栈
使用线程池实现高并发会更有可能出现一些意想不到的异常,而线程池往往可能会“吃”点这些异常,就是我们常说的Runnable没有返回值,不能抛出受检查的异常。让我们的结果有误但是却不给我们异常信息。要排查这些异常找出问题所在,我们就要分析异常堆栈,追踪问题出在哪里。
让线程池返回异常堆栈的信息,最简单的就是在线程池提交任务的时候不用submit(),而是改用execute()方法
Future<?> submit(Runnable task);位于java.util.concurrent.ExecutorService接口中
void execute(Runnable command);位于java.util.concurrent.Executor接口
比如下面这个例子,在程序中让100除以5个数,其中一个数是0,本来要抛出异常,可是使用submit却没有
package xidian.lili.reentrantlock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DivTask implements Runnable{
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 pool=new ThreadPoolExecutor(0,Integer.MAX_VALUE,0L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
for(int i=0;i<5;i++){
DivTask task=new DivTask(100,i);
pool.submit(task);
//pool.execute(task);
}
// System.out.println(100/0);
}
}
结果:
改用execute(task),可以看到部分堆栈信息
或者也可以使用下面的submit,也可以捕获部分堆栈信息。我们常说Callable是有返回值的。Future一般我们认为是Callable的返回值,但他其实代表的是任务的生命周期(当然了,它是能获取得到Callable的返回值的)
Future re=pool.submit(task);
try {
re.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
结果:
但是这样的异常只告诉我们异常是哪里抛出的,看不到任务是在哪里提交的
所以扩展ThreadPoolExecutor,重写submit方法和execute方法
和上一篇扩展方式不同,上一篇再用内名内部类,简单的重写了三个方法,进行拓展
这次我们通过继承实现新的带追踪追站的我们自己的线程池类,通过继承,我们调用父类的构造方法创建线程池,但是重写submit和execute方法,其实就是对提交过来的任务进行包装成一个新的任务。
command使我们原本提交过来的任务,我们在包装任务(wrap是想runnable接口)中执行这个任务,并用try-catch捕获并抛出任务执行中可能遇到的异常
其实就是run方法嵌套
private Runnable wrap(Runnable command, Exception clientTrace, String Threadname) {
return new Runnable(){
@Override
public void run() {
try{
command.run();//执行原本任务时捕获异常
}catch(Exception e){
clientTrace.printStackTrace();
throw e;//抛出原本任务中的异常
}
}
};
}
package xidian.lili.reentrantlock;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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 command) {
super.execute(wrap(command,clientTrace(),Thread.currentThread().getName()));
}
private Runnable wrap(Runnable command, Exception clientTrace, String Threadname) {
return new Runnable(){
@Override
public void run() {
try{
command.run();
}catch(Exception e){
clientTrace.printStackTrace();
throw e;
}
}
};
}
private Exception clientTrace() {
return new Exception("Client stack trace");
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(wrap(task,clientTrace(),Thread.currentThread().getName()));
}
}
这样我们用我们自己的线程池类创建线程池并进行实验
package xidian.lili.reentrantlock;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DivTaskTrace implements Runnable{
int a,b;
public DivTaskTrace(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 pool=new TraceThreadPoolExecutor(0,Integer.MAX_VALUE,0L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
for(int i=0;i<5;i++){
DivTask task=new DivTask(100,i);
//pool.submit(task);
/*
Future re=pool.submit(task);
try {
re.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
*/
pool.execute(task);
}
}
}
得到的结果:
在java除法中,允许浮点数运算时除数为零,所得结果是Infinity