当线程中发生异常时,SchduledExecutorService 不执行 UncaughtExceptionHandler

SchduledExecutorService not executing UncaughtExceptionHandler when exception happens in the thread

我正在使用 ScheduledExecutorService,它使用 ThreadFactory 创建新线程。但是当定时任务出现异常时,threadfactory中的UncaughtExceptionHandler不会被执行。为什么会这样?

线程工厂如下:

public class CustomThreadFactory {
  public static ThreadFactory defaultFactory(String namePrefix) {
    return new ThreadFactoryBuilder()
      .setNameFormat(String.format("%s-%%d", namePrefix))
      .setDaemon(false)
      .setUncaughtExceptionHandler(new CustomExceptionHandler())
      .build();
  }
}

异常处理程序:

public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler {
  @Override
  public void uncaughtException(Thread thread, Throwable t) {
    log.error("Received uncaught exception {}", thread.getName(), t);
  }
}

主要功能:

public static void main(String[] args) {
  ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
          CustomThreadFactory.defaultFactory("scheduler"));
  ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 20L,
          TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1));

  scheduler.scheduleWithFixedDelay(
      () -> {
          executor.submit(() -> Thread.sleep(10000));
      },0,1,TimeUnit.SECONDS);
}

我正在陈述我的确切问题,这样它就不会成为 XY 问题。在上面的代码片段中,阻塞队列的大小为 1,并且每秒都会将任务添加到阻塞队列中。所以在添加第三个任务时,阻塞队列执行器给出 RejectionExecutionException ,它不是由 UncaughtExceptionHandler.

打印的

当线程由于未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler() 查询线程的 UncaughtExceptionHandler 并将调用处理程序的 uncaughtException方法,将线程和异常作为参数传递。

UncaughtExceptionHandler 将在提交的任务抛出未捕获的异常但 RejectionExecutionException 没有被任务本身抛出时调用。但是,您可以将 RejectedExecutionHandler 传递给 ThreadPoolExecutor

    public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

ThreadPoolExecutor 将 RejectedExecutionHandler 作为参数。

编辑:

在你的情况下 UncaughtExceptionHandler 不是 called/notified 因为当你调用 scheduleWithFixedDelay 时不会直接执行你的 Runnable 但是它会在 ScheduledFutureTask 中扭曲它。现在,当 executor.submit(() -> Thread.sleep(10000)); 抛出异常(在您的情况下为 RejectionExecutionException )时,它不会保持未捕获状态,因为 FutureTask#run 捕获了异常并将异常设置为 Future result/outcome。看FutureTask#run的部分:

try {
    result = c.call();
    ran = true;
} catch (Throwable ex) {
    result = null;
    ran = false;
    setException(ex);
}

setException()方法将异常对象设置为FutureTaskoutcome

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

现在的问题是我们能否以某种方式获得异常的 exception/notification?

是的,这是可能的。当您在 Future 上调用 Future#get 时,将抛出异常,但 Exception 将被包裹在 ExecutionException 中。看看 Future#get 方法。

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

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

我已经在 ScheduledThreadPoolExecutor 上看到了所有可用的方法(提交、执行、计划等),将 task 包装在未来的任务中。所以我认为没有办法通过 UncaughtExceptionHandler 以防万一。

但是对于 ThreadPoolExecutor,如果您使用 ThreadPoolExecutor#submit 提交任务,您的 UncaughtExceptionHandler 不会 收到通知,但如果您使用 ThreadPoolExecutor#execute 然后 UncaughtExceptionHandler 将收到通知,因为 ThreadPoolExecutor#execute 没有将您的任务包装在 Future 中。