背景

最近在使用线程池的过程中,发现线程池执行的任务报错后没有错误日志打出来。纳尼?!线程池竟然还有这个坑?都说源码面前无秘密,所以我翻看了一下Java源码,客官请往下看:

源码分析

1. submit源码分析

由于这个不打印异常日志的问题是因为使用submit方法引起的,所以我们先看一下submit方法的源码:

/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException       {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
  if (task == null) throw new NullPointerException();
  RunnableFuture<Void> ftask = newTaskFor(task, null);
  execute(ftask);
  return ftask;
}

我们可以看到这个方法把Runnable类型的任务转换成了Future类型的任务。我们知道,线程最后执行是通过调用run方法,那我们再深入看一下Future中的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);
      }
      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);
  }
}

我们可以看到在Future的run方法中有个try-catch,所以异常在这里被处理了,这就是在我们仅仅使用submit方法,异常日志没有打印出来的原因。

实际上,通过submit提交的任务,可以通过返回的Future对象的get方法拿到结果,我们再跟踪一下Future的get方法:

/**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

我们可以看到最后调用了report方法:

/**
     * Returns result or throws exception for completed task.
     *
     * @param s completed state value
     */
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

这里我们可以看到如果线程的状态不是正常的,那么就会抛出异常,而这个异常正是在run的时候被捕获的异常对象。所以其实通过submit提交的任务也可以看到异常日志,只不过需要调用get方法才能看到。

2. execute源码分析

而用execute执行的任务在运行过程中报错是可以打印错误日志的,那我们再来看一下execute的源码:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
              int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
}

在这里并没有处理异常的地方,所以在执行任务的过程中如果发生异常会直接打印出来。

解决方法

如果我们只是提交任务,不管任务的执行结果,并且需要打印任务执行过程中的错误,那么我们可以就要使用execute方法。如果我们需要知道任务执行之后的结果,就要使用submit方法。

如果你非要使用submit,并且想看到运行过程中的错误日志,那么也不是没有办法。其中重写ThreadPoolExecutor的afterExecute方法就可以实现。比如这样:

public class MyThreadPoolExecutor extends ThreadPoolExecutor {
    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);

        if (t == null && r instanceof Future<?>) {
            try {
                Object result = ((Future<?>) r).get();
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt(); // ignore/reset
            }
        }
        if (t != null) {
            t.printStackTrace();
        }

    }
}

这个afterExecute方法会在线程执行完后回调,所以有异常也会通过我们重写的方法打印日志出来。然后你直接使用你这个自定义的线程池并且使用submit方法在运行时碰到异常就可以打印出日来了。

总结

所以这其实也不是坑哈,只是我们在使用线程池的过程中没有对这两个方法做更多的了解,所以只要你理解了我上面的源码分析,你以后就知道在什么场合该用什么方法去提交任务了。谢谢客官观看!



技术分享      线程池

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!