SchduledExecutorService не выполняет UncaughtExceptionHandler, когда исключение происходит в потоке - PullRequest
1 голос
/ 10 октября 2019

Я использую ScheduledExecutorService, который использует 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. В приведенном выше фрагменте очередь блокировки имеет размер один, и задачи добавляются в очередь блокировки каждую секунду. Таким образом, при добавлении третьего задания исполнитель очереди блокировки выдает RejectionExecutionException, который не печатается UncaughtExceptionHandler.

1 Ответ

0 голосов
/ 10 октября 2019

Когда поток собирается завершить работу из-за неперехваченного исключения, виртуальная машина Java запросит поток для его UncaughtExceptionHandler, используя Thread.getUncaughtExceptionHandler (), и вызовет метод 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 не вызывается / не уведомляется, потому что при вызове scheduleWithFixedDelay не выполняет ваш Runnable напрямую, однако он деформируется в ScheduledFutureTask. Теперь, когда executor.submit(() -> Thread.sleep(10000)); выдает исключение (в вашем случае RejectionExecutionException), оно не остается необработанным, поскольку FutureTask#run перехватило исключение и установило исключение как Будущий результат / результат. Посмотрите на часть метода FutureTask#run:

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

И setException(), установленного для объекта исключения outcome из FutureTask.

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

Теперь вопрос, можем ли мы каким-то образом получить исключение / уведомление об Исключении?

Да, это возможно. Когда вы назовете 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.

...