【问题标题】:SchduledExecutorService not executing UncaughtExceptionHandler when exception happens in the thread当线程中发生异常时,SchduledExecutorService 不执行 UncaughtExceptionHandler
【发布时间】:2019-10-10 08:42:45
【问题描述】:

我正在使用 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 问题。在上面的 sn-p 中,阻塞队列的大小为 1,并且每秒将任务添加到阻塞队列中。所以在添加第三个任务时,阻塞队列执行器会给出RejectionExecutionException,它不是由UncaughtExceptionHandler打印的。

【问题讨论】:

  • RejectionExecutionException 由java.util.concurrent.RejectedExecutionHandler 处理,而不是由UncaughtExceptionHandler 处理,后者仅用于在任务执行期间引发的异常。 ThreadPoolExecutor 具有允许您指定自己的 RejectedExecutionHandler 的构造函数

标签: java scheduler scheduledexecutorservice uncaughtexceptionhandler


【解决方案1】:

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

如果提交的任务抛出未捕获的异常但RejectionExecutionException 不会由任务本身抛出,则会调用UncaughtExceptionHandler。但是,您可以将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 不会被调用/通知,因为当您调用 scheduleWithFixedDelay 时不会直接执行您的 Runnable,但它会在 ScheduledFutureTask 中扭曲它。现在,当 executor.submit(() -&gt; Thread.sleep(10000)); 抛出异常(在您的情况下为 RejectionExecutionException )时,它不会保持未被捕获,因为 FutureTask#run 捕获了异常并将异常设置为 Future 结果/结果。看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();
    }
}

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

是的,这是可能的。当您在 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 将被通知为 @987654350 @ 不会将您的任务包装在 Future 中。

【讨论】:

  • 在这种情况下,实际任务是不是executor.submit(() -&gt; Thread.sleep(10000)); 而不仅仅是Thread.sleep(10000)? .并且 RejectionExecutionException 是由提交函数抛出的。
  • 但我明白了。当 JVM 遇到 RejectionExecutionException 时,它不会检查 UncaughtExceptionHandler 而只检查 RejectionExecutionHandler。
  • @ShashwatKumar 我明白你的意思。我需要更深入地研究代码。如果我找到任何东西,我会告诉你的。
  • @ShashwatKumar 我已经更新了我的答案。请看一下。我希望它会帮助你。谢谢!!!
猜你喜欢
  • 2017-11-18
  • 2013-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-12
  • 1970-01-01
  • 2019-01-27
  • 2013-12-05
相关资源
最近更新 更多