Java ThreadPoolExecutor submit catch exception

代码

public static void main(String[] args) {
    final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
      
    Future<?> future = executorService.submit(() -> {
        int a= 1/0;
    });

    System.out.println("hello world");
}

output

hello world

这个代码很简单, 使用ThreadPoolExecutor实例submit方法执行任务,如果代码中抛出异常,就会被吞掉。

为什么?

结论:
因为submit()返回的是Future对象,你没有使用get()取结果,你就拿不到任何结果,包括异常。
这点其实和C#中的Task有些类似,这是微软官方的例子:

https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library

public class Program
{
  public class CustomException : Exception
  {
      public CustomException(String message) : base(message)
      { }
  }

  public static void Main(string[] args)
  {
      var task1 = Task.Run(() => { throw new CustomException("This exception is expected!"); });

      try
      {
          //task1.Wait();
      }
      catch (AggregateException ae)
      {
          foreach (var e in ae.InnerExceptions)
          {
              // Handle the custom exception.
              if (e is CustomException)
              {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else
              {
                  throw e;
              }
          }
      }

      Console.ReadKey();
  }
}

如果没有task1.Wait(),异常是不会抛出的。

内部原理

我们使用VS Code查看源代码,一路下去看看:

  1. task就是我们自己定义的任务,使用newTaskFor包装下
/**
   * @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;
  }
  1. 包装了啥?哦,FutureTask
/**
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

把我们的任务传入FutureTask,作为callbale属性

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}
  1. 我们看 execute(ftask)
 public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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);
}

firstTask就是刚才包装的FutureTask,实例化Worker的时候将FutureTask传入,存入
Worker实例持有一个thread对象,在worker被add之后start

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (int c = ctl.get();;) {
        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;

        for (;;) {
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int c = ctl.get();

                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && firstTask == null)) {
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    workerAdded = true;
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
  1. 看下Worker类
 Worker(Runnable firstTask) {
      setState(-1); // inhibit interrupts until runWorker
      this.firstTask = firstTask;
      this.thread = getThreadFactory().newThread(this);
}

Worker类实现Runnable,所以thread.start()后,会执行run方法

  1. 看下Worker的run方法
public void run() {
      runWorker(this);
}

我们的FutureTask一路被整在了这里,赋值给task, task.run()执行的就是FutureTask的run方法

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                try {
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
  1. 看下FutureTask的run方法
public void run() {
    if (state != NEW ||
        !RUNNER.compareAndSet(this, 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);
    }
}

刚第2步时候说了,我们自定义的任务赋值给了callable属性
所以c.call()就是执行我们自定义的任务代码
如果发生异常,我们看到catch代码块中执行了setException(ex)

protected void setException(Throwable t) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) {
        outcome = t;
        STATE.setRelease(this, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

最终把异常存入了outcome
什么时候才会使用outcome?report方法

@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);
}

没错,就是get()方法调用report()

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

稍微退后一点,如果我们的代码没有异常,就会把执行结果也放入这个outcome中

protected void set(V v) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) {
        outcome = v;
        STATE.setRelease(this, NORMAL); // final state
        finishCompletion();
    }
}

优雅处理异常

回到文章最开始,因为我们执行任务并没有(不关心)返回值,所以不会调用get()方法,任务出现问题也会装的静悄悄,我们连日志都没有写。
所以简单的办法就是给我们的任务加一个try catch代码块,好歹写个日志。

那么更优雅的方式是利用现有的原理去扩展,我们回到第5步,也就是FutureTask实例执行的时候:

try {
    beforeExecute(wt, task);
    try {
        task.run();
        afterExecute(task, null);
    } catch (Throwable ex) {
        afterExecute(task, ex);
        throw ex;
    }
}

看到没,beforeExecute,afterExecute,有点回调的意思

/**
 * Method invoked upon completion of execution of the given Runnable.
 * This method is invoked by the thread that executed the task. If
 * non-null, the Throwable is the uncaught {@code RuntimeException}
 * or {@code Error} that caused execution to terminate abruptly.
 *
 * <p>This implementation does nothing, but may be customized in
 * subclasses. Note: To properly nest multiple overridings, subclasses
 * should generally invoke {@code super.afterExecute} at the
 * beginning of this method.
 *
 * <p><b>Note:</b> When actions are enclosed in tasks (such as
 * {@link FutureTask}) either explicitly or via methods such as
 * {@code submit}, these task objects catch and maintain
 * computational exceptions, and so they do not cause abrupt
 * termination, and the internal exceptions are <em>not</em>
 * passed to this method. If you would like to trap both kinds of
 * failures in this method, you can further probe for such cases,
 * as in this sample subclass that prints either the direct cause
 * or the underlying exception if a task has been aborted:
 *
 * <pre> {@code
 * class ExtendedExecutor extends ThreadPoolExecutor {
 *   // ...
 *   protected void afterExecute(Runnable r, Throwable t) {
 *     super.afterExecute(r, t);
 *     if (t == null
 *         && r instanceof Future<?>
 *         && ((Future<?>)r).isDone()) {
 *       try {
 *         Object result = ((Future<?>) r).get();
 *       } catch (CancellationException ce) {
 *         t = ce;
 *       } catch (ExecutionException ee) {
 *         t = ee.getCause();
 *       } catch (InterruptedException ie) {
 *         // ignore/reset
 *         Thread.currentThread().interrupt();
 *       }
 *     }
 *     if (t != null)
 *       System.out.println(t);
 *   }
 * }}</pre>
 *
 * @param r the runnable that has completed
 * @param t the exception that caused termination, or null if
 * execution completed normally
 */
protected void afterExecute(Runnable r, Throwable t) { }

瞧瞧人家的注释...

最终代码

public class MonitoringThreadPoolExecutor extends ThreadPoolExecutor {

  public MonitoringThreadPoolExecutor(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) {
      if (r instanceof Future<?>) {
               super.afterExecute(r, t);
        if (t == null
            && r instanceof Future<?>
            && ((Future<?>)r).isDone()) {
          try {
            Object result = ((Future<?>) r).get();
          } catch (CancellationException ce) {
            t = ce;
         } catch (ExecutionException ee) {
            t = ee.getCause();
         } catch (InterruptedException ie) {
            // ignore/reset
            Thread.currentThread().interrupt();
          }
        }
       if (t != null)
          System.out.println(t.getMessage());
      }
  }
}
public static void main(String[] args) {
  final ExecutorService executorService = new MonitoringThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
  executorService.submit(() -> {
    int a= 1/0;
  });

  System.out.println("hello world");
}

output

hello world
/ by zero
posted @ 2022-03-04 18:17  talentzemin  阅读(128)  评论(0编辑  收藏  举报