你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

提交到线程池的异常被吃了

2021/12/26 2:07:44

如果我们是使用submit提交的任务,那么就要注意异常的处理了。

先来看一段代码:

ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(() -> {
    System.out.println("任务开始执行...");
    int a = 10 / 0;	// 抛出异常
});
executorService.shutdown();

很明显,我们提交的任务会抛出异常,但实际上,控制台并不会打印任何异常的信息。那异常到底被谁吃了?

那我们就需要看看我们提交任务后,线程池做了什么手脚了,在AbstractExecutorService类中:

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);	//该方法会返回一个FutureTask
    // 狸猫换太子了,execute执行的任务是处理过的
    execute(ftask);
    return ftask;
}

// 实际上就是对我们的任务又进一步封装了
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

可以看到,我们提交的task会被封装成一个FutureTask,最后交给线程池执行的实际上是这个FutureTask。FutureTask的结构如下:
在这里插入图片描述
可以看出FutureTask也实现了Runnable接口,所以到时候FutureTask的run()方法就会被调用:

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
           		// 在这里才会真正的去执行我们提交的任务
                result = c.call();		
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);		//将异常信息塞到outcome中
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;	// 将异常信息设置到outcome中
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

看到没有,FutureTask使用try-catch捕获了任务执行的异常,并通过setException方法将异常塞到了outcome中,因此我们的异常被吃了,破案了。

那怎么拿到我们的异常信息?FutureTask的get方法会返回outcome,因此调用FutureTask的get方法就可以拿到异常信息。

ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<Integer> future = executorService.submit(() -> {
    System.out.println("任务开始执行...");
    int a = 10 / 0;
    return a;
});
future.get();	//调用get方法
System.out.println("任务执行结束...");
executorService.shutdown();

在这里插入图片描述
当然,知道了异常是怎么被吃掉的,我们就可以自己捕捉异常。

ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(() -> {
	// 自己又try-catch捕获异常
    try {
        System.out.println("任务开始执行...");
        int a = 10 / 0;
        return a;
    }catch (Exception e){
        System.out.println(e);
    }
    return null;
});

对于execute()方式执行的任务,虽然异常不会被吃掉,但依旧推荐我们自己try-catch做一下异常处理