ScheduledExecutorService异常处理

发布于 2024-11-27 09:20:31 字数 513 浏览 0 评论 0 原文

我使用 ScheduledExecutorService 定期执行一个方法。

p-code:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 //Do business logic, may Exception occurs
             }
        }, 1, 10, TimeUnit.SECONDS);

我的问题:

如果 run() 抛出异常,如何继续调度程序? 我应该尝试捕获方法 run() 中的所有异常吗?或者任何内置的回调方法来处理异常?谢谢!

I use ScheduledExecutorService to execute a method periodically.

p-code:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 //Do business logic, may Exception occurs
             }
        }, 1, 10, TimeUnit.SECONDS);

My question:

How to continue the scheduler, if run() throws Exception?
Should I try-catch all Exception in method run()? Or any built-in callback method to handle the Exception? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

梦幻之岛 2024-12-04 09:20:31

tl;dr

任何转义 run 方法的异常都会停止所有进一步的工作,恕不另行通知。

始终在 run 方法中使用 try-catch。如果您希望继续安排的活动,请尝试恢复。

@Override
public void run ()
{
    try {
        doChore();
    } catch ( Exception e ) { 
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
}

问题问题

涉及 的关键技巧ScheduledExecutorService任何到达执行器的抛出异常或错误都会导致执行器停止。不再对 Runnable 进行调用,不再工作已完成。这次停工悄然发生,您不会被通知。这种顽皮的语言博客文章有趣地讲述了了解这种行为的艰难过程。

解决方案

yegor256的回答arun_suresh 的回答 似乎基本上都是正确的。这些答案有两个问题:

  • 捕获错误和异常
  • 有点复杂

错误异常?

在Java中,我们通常只捕获异常,而不是错误。但在 ScheduledExecutorService 的这种特殊情况下,未能捕获其中任何一个都将意味着工作停止。所以你可能想两者都抓住。我对此不是 100% 确定,不完全了解捕获所有错误的含义。如果需要请纠正我。

捕获错误和异常的原因之一可能涉及在任务中使用库。请参阅 jannis 的评论

捕获异常和错误的一种方法是捕获它们的超类, Throwable 为例。

} catch ( Throwable t ) {

...而不是...

} catch ( Exception e ) {

最简单的方法:只需添加一个 Try-Catch

但这两个答案都有点复杂。只是为了记录,我将展示最简单的解决方案:

始终将 Runnable 的代码包装在 Try-Catch 中,以捕获任何和所有异常错误。

Lambda 语法

使用 lambda(在 Java 8 及更高版本中)。

final Runnable someChoreRunnable = () -> {
    try {
        doChore();
    } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
};

老式语法

在 lambda 之前的老式方式。

final Runnable someChoreRunnable = new Runnable()
{
    @Override
    public void run ()
    {
        try {
            doChore();
        } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
            logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
        }
    }
};

在每个 Runnable/Callable 中,

无论 ScheduledExecutorService,对我来说,在任何中始终使用通用try-catch(Exception† e)似乎是明智的运行 Runnable< 的方法/a>.对于任何 调用也是如此 方法rel="noreferrer">可调用


完整的示例代码

在实际工作中,我可能会单独定义 Runnable 而不是嵌套。但这是一个简洁的一体化示例。

package com.basilbourque.example;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 *  Demo `ScheduledExecutorService`
 */
public class App {
    public static void main ( String[] args ) {
        App app = new App();
        app.doIt();
    }

    private void doIt () {

        // Demonstrate a working scheduled executor service.
        // Run, and watch the console for 20 seconds.
        System.out.println( "BASIL - Start." );

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        ScheduledFuture < ? > handle =
                scheduler.scheduleWithFixedDelay( new Runnable() {
                    public void run () {
                        try {
                            // doChore ;   // Do business logic.
                            System.out.println( "Now: " + ZonedDateTime.now( ZoneId.systemDefault() ) );  // Report current moment.
                        } catch ( Exception e ) {
                            // … handle exception/error. Trap any unexpected exception here rather to stop it reaching and shutting-down the scheduled executor service.
                            // logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + e.getStackTrace() );
                        }   // End of try-catch.
                    }   // End of `run` method.
                } , 0 , 2 , TimeUnit.SECONDS );


        // Wait a long moment, for background thread to do some work.
        try {
            Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) );
        } catch ( InterruptedException e ) {
            e.printStackTrace();
        }

        // Time is up. Kill the executor service and its thread pool.
        scheduler.shutdown();

        System.out.println( "BASIL - Done." );

    }
}

运行时。

BASIL - 开始。

现在:2018-04-10T16:46:01.423286-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:03.449178-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:05.450107-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:07.450586-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:09.456076-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:11.456872-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:13.461944-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:15.463837-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:17.469218-07:00[美国/洛杉矶]

现在:2018-04-10T16:46:19.473935-07:00[美国/洛杉矶]

巴兹尔 - 完成。

另一个例子

这是另一个例子。在这里,我们的任务打算运行大约二十次,每五秒运行一次,持续一分钟。但在第五次运行时,我们抛出异常。

public class App2
{
    public static void main ( String[] args )
    {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        final AtomicInteger counter = new AtomicInteger( 0 );
        Runnable task = ( ) -> {
            int c = counter.incrementAndGet();
            if ( c > 4 )
            {
                System.out.println( "THROWING EXCEPTION at " + Instant.now() );
                throw new IllegalStateException( "Bogus exception. c = " + c + ". " + Instant.now() ); // Notice how this exception is silently swallowed by the scheduled executor service, while causing a work stoppage.
            }
            System.out.println( "Task running. c = " + c + ". " + Instant.now() );
        };
        ses.scheduleAtFixedRate( task , 0 , 5 , TimeUnit.SECONDS );

        try { Thread.sleep( Duration.ofMinutes( 1 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }
        System.out.println( "Main thread done sleeping. " + Instant.now() );

        ses.shutdown();
        try { ses.awaitTermination( 1 , TimeUnit.MINUTES ); }catch ( InterruptedException e ) { e.printStackTrace(); }
    }
}

运行时。

Task running. c = 1. 2021-10-14T20:09:16.317995Z
Task running. c = 2. 2021-10-14T20:09:21.321536Z
Task running. c = 3. 2021-10-14T20:09:26.318642Z
Task running. c = 4. 2021-10-14T20:09:31.318320Z
THROWING EXCEPTION at 2021-10-14T20:09:36.321458Z
Main thread done sleeping. 2021-10-14T20:10:16.320430Z

注意:

  • 异常被预定的执行器服务默默地吞掉。
  • 发生停工。我们的任务没有进一步执行的计划。又是一个无声的问题。

因此,当您的任务抛出异常时,您可能会得到最糟糕的结果:无声地停止工作,没有任何解释。

解决方案,如上所述:始终run 方法中使用 try-catch


† 或者也许Throwable 而不是 异常< /a> 捕捉 Error 对象也是如此。

tl;dr

Any exception escaping your run method halts all further work, without notice.

Always use a try-catch within your run method. Try to recover if you want scheduled activity to continue.

@Override
public void run ()
{
    try {
        doChore();
    } catch ( Exception e ) { 
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
}

The Problem

The question refers to the critical trick with a ScheduledExecutorService: Any thrown exception or error reaching the executor causes the executor to halt. No more invocations on the Runnable, no more work done. This work stoppage happens silently, you'll not be informed. This naughty-language blog posting entertainingly narrates the hard way to learn about this behavior.

The Solution

The answer by yegor256 and the answer by arun_suresh both seem to be basically correct. Two issues with those answers:

  • Catch errors as well as exceptions
  • A bit complicated

Errors and Exceptions ?

In Java we normally catch only exceptions, not errors. But in this special case of ScheduledExecutorService, failing to catch either will mean a work stoppage. So you may want to catch both. I'm not 100% sure about this, not knowing fully the implications of catching all errors. Please correct me if needed.

One reason to catch errors as well as exceptions might involve the use of libraries within your task. See the comment by jannis.

One way to catch both exceptions and errors is to catch their superclass, Throwable for an example.

} catch ( Throwable t ) {

…rather than…

} catch ( Exception e ) {

Simplest Approach: Just Add a Try-Catch

But both answers are a bit complicated. Just for the record, I'll show the simplest solution:

Always wrap your Runnable's code in a Try-Catch to catch any and all exceptions and errors.

Lambda Syntax

With a lambda (in Java 8 and later).

final Runnable someChoreRunnable = () -> {
    try {
        doChore();
    } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
};

Old-Fashioned Syntax

The old-fashioned way, before lambdas.

final Runnable someChoreRunnable = new Runnable()
{
    @Override
    public void run ()
    {
        try {
            doChore();
        } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
            logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
        }
    }
};

In Every Runnable/Callable

Regardless of a ScheduledExecutorService, it seems sensible to me to always use a general try-catch( Exception† e ) in any run method of a Runnable. Ditto for any call method of a Callable.


Complete example code

In real work, I would likely define the Runnable separately rather than nested. But this makes for neat all-in-one example.

package com.basilbourque.example;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 *  Demo `ScheduledExecutorService`
 */
public class App {
    public static void main ( String[] args ) {
        App app = new App();
        app.doIt();
    }

    private void doIt () {

        // Demonstrate a working scheduled executor service.
        // Run, and watch the console for 20 seconds.
        System.out.println( "BASIL - Start." );

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        ScheduledFuture < ? > handle =
                scheduler.scheduleWithFixedDelay( new Runnable() {
                    public void run () {
                        try {
                            // doChore ;   // Do business logic.
                            System.out.println( "Now: " + ZonedDateTime.now( ZoneId.systemDefault() ) );  // Report current moment.
                        } catch ( Exception e ) {
                            // … handle exception/error. Trap any unexpected exception here rather to stop it reaching and shutting-down the scheduled executor service.
                            // logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + e.getStackTrace() );
                        }   // End of try-catch.
                    }   // End of `run` method.
                } , 0 , 2 , TimeUnit.SECONDS );


        // Wait a long moment, for background thread to do some work.
        try {
            Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) );
        } catch ( InterruptedException e ) {
            e.printStackTrace();
        }

        // Time is up. Kill the executor service and its thread pool.
        scheduler.shutdown();

        System.out.println( "BASIL - Done." );

    }
}

When run.

BASIL - Start.

Now: 2018-04-10T16:46:01.423286-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:03.449178-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:05.450107-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:07.450586-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:09.456076-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:11.456872-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:13.461944-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:15.463837-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:17.469218-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:19.473935-07:00[America/Los_Angeles]

BASIL - Done.

Another example

Here is another example. Here our task is meant to run about twenty times, once every five seconds for a minute. But on the fifth run, we throw an exception.

public class App2
{
    public static void main ( String[] args )
    {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        final AtomicInteger counter = new AtomicInteger( 0 );
        Runnable task = ( ) -> {
            int c = counter.incrementAndGet();
            if ( c > 4 )
            {
                System.out.println( "THROWING EXCEPTION at " + Instant.now() );
                throw new IllegalStateException( "Bogus exception. c = " + c + ". " + Instant.now() ); // Notice how this exception is silently swallowed by the scheduled executor service, while causing a work stoppage.
            }
            System.out.println( "Task running. c = " + c + ". " + Instant.now() );
        };
        ses.scheduleAtFixedRate( task , 0 , 5 , TimeUnit.SECONDS );

        try { Thread.sleep( Duration.ofMinutes( 1 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }
        System.out.println( "Main thread done sleeping. " + Instant.now() );

        ses.shutdown();
        try { ses.awaitTermination( 1 , TimeUnit.MINUTES ); }catch ( InterruptedException e ) { e.printStackTrace(); }
    }
}

When run.

Task running. c = 1. 2021-10-14T20:09:16.317995Z
Task running. c = 2. 2021-10-14T20:09:21.321536Z
Task running. c = 3. 2021-10-14T20:09:26.318642Z
Task running. c = 4. 2021-10-14T20:09:31.318320Z
THROWING EXCEPTION at 2021-10-14T20:09:36.321458Z
Main thread done sleeping. 2021-10-14T20:10:16.320430Z

Notice:

  • The exception is silently swallowed by the scheduled executor service.
  • A work stoppage occurs. No further executions of our task are scheduled. Again, a silent problem.

So when your task throws an exception, you get the worst outcome possible: Silent work stoppage with no explanation.

The solution, as mentioned above: Always use a try-catch within your run method.


† Or perhaps Throwable instead of Exception to catch Error objects too.

吹泡泡o 2024-12-04 09:20:31

您应该使用 scheduler.scheduleWithFixedDelay(...) 返回的 ScheduledFuture 对象,如下所示:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 throw new RuntimeException("foo");
             }
        }, 1, 10, TimeUnit.SECONDS);

// Create and Start an exception handler thread
// pass the "handle" object to the thread
// Inside the handler thread do :
....
try {
  handle.get();
} catch (ExecutionException e) {
  Exception rootException = e.getCause();
}

You should use the ScheduledFuture object returned by your scheduler.scheduleWithFixedDelay(...) like so :

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 throw new RuntimeException("foo");
             }
        }, 1, 10, TimeUnit.SECONDS);

// Create and Start an exception handler thread
// pass the "handle" object to the thread
// Inside the handler thread do :
....
try {
  handle.get();
} catch (ExecutionException e) {
  Exception rootException = e.getCause();
}
好倦 2024-12-04 09:20:31

老问题,但接受的答案没有给出解释,并提供了一个糟糕的例子,而最受支持的答案在某些方面是正确的,但最终鼓励您在每个 Runnable.run( ) 方法。
我不同意,因为:

  • 它不简洁:不是任务捕获自身异常的标准。
  • 它并不健壮:新的 Runnable 子类可能会忘记执行异常捕获和关联的故障转移。
  • 它克服了任务带来的低耦合性,因为它将要执行的任务与处理任务结果的方式耦合在一起。
  • 它混合了职责:这不是处理异常或将异常传达给调用者的任务职责。任务是要执行的事情。

我认为异常传播应该由 ExecutorService 框架执行,实际上它提供了该功能。
此外,试图通过短路 ExecutorService 的工作方式来变得太聪明也不是一个好主意:框架可能会发展,而您希望以标准方式使用它。
最后,让 ExecutorService 框架完成其工作并不意味着一定要停止后续的调用任务。
如果计划任务遇到问题,调用者有责任根据问题原因重新计划或不计划任务。
每一层都有其职责。保留这些使代码既清晰又可维护。


ScheduledFuture.get() :捕获任务中发生的异常和错误的正确 API

ScheduledExecutorService.scheduleWithFixedDelay()/scheduleAtFixRate() 规范中的状态:

如果任务的任何执行遇到异常,后续
处决受到压制。否则,任务只会通过以下方式终止
取消或终止执行人。

这意味着 ScheduledFuture.get() 不会在每次计划调用时返回,而是在最后一次调用任务时返回,即任务取消:由 ScheduledFuture.cancel( 引起) ) 或任务中抛出的异常。
因此,处理 ScheduledFuture 返回以使用 ScheduledFuture.get() 捕获异常看起来是正确的:

  try {
    future.get();

  } catch (InterruptedException e) {
    // ... to handle
  } catch (ExecutionException e) {
    // ... and unwrap the exception OR the error that caused the issue
    Throwable cause = e.getCause();       
  }

默认行为示例:如果任务执行之一遇到问题,则停止调度

它执行的任务在第三次执行时抛出异常并终止调度。
在某些情况下,我们希望如此。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    Future<?> futureA = executor
        .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
    try {
      System.out.println("before get()");
      futureA.get(); // will return only if canceled
      System.out.println("after get()");
    } catch (InterruptedException e) {
      // handle that : halt or no
    } catch (ExecutionException e) {
      System.out.println("exception caught :" + e.getCause());
    }

    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");
      if (invocationDone.decrementAndGet() == 0) {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

输出 :

before get()
pool-1-thread-1, execution
pool-1-thread-1, execution
pool-1-thread-1, execution
exception caught :java.lang.IllegalArgumentException: ohhh an Exception in MyRunnable

如果其中一个任务执行遇到问题,则可以继续进行调度的示例

它执行的任务在前两次执行时抛出异常,并在第三次执行时抛出错误。
我们可以看到任务的客户端可以选择停止或不停止调度:这里我在出现异常的情况下继续,在出现错误的情况下停止。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    boolean mustHalt = true;
    do {
      Future<?> futureA = executor
              .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
      try {
        futureA.get(); // will return only if canceled
      } catch (InterruptedException e) {
        // handle that : halt or not halt
      } catch (ExecutionException e) {
        if (e.getCause() instanceof Error) {
          System.out.println("I halt in case of Error");
          mustHalt = true;
        } else {
          System.out.println("I reschedule in case of Exception");
          mustHalt = false;
        }
      }
    }
    while (!mustHalt);
    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");

      if (invocationDone.decrementAndGet() == 0) {
        throw new Error("ohhh an Error in MyRunnable");
      } else {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

输出 :

pool-1-thread-1, execution
I reschedule in case of Exception
pool-1-thread-1, execution
I reschedule in case of Exception
pool-1-thread-2, execution
I halt in case of Error

Old question but the accepted answer doesn't give explanations and provides a poor example and the most upvoted answer is right on some points but finally encourages you to add catch exceptions in every Runnable.run() method.
I disagree because :

  • it is not neat : not standard for a task to catch its own exceptions.
  • it is not robust : a new Runnable subclass could forget to perform the exception catch and the failover associated.
  • it defeats the low coupling promoted by tasks since that couples the tasks to execute with the way of handling the task result.
  • it mixes responsibilities : that is not the task responsibility to handle the exception or to communicate the exception to the caller. A task is something to execute.

I think that the exception propagation should be performed by the ExecutorService framework and actually it offers that feature.
Besides, trying to be too clever by trying to short-circuiting the ExecutorService way of working is not a good idea either : the framework may evolve and you want to use it in a standard way.
At last, letting the ExecutorService framework to make its job doesn't mean necessarily halting the subsequent invocations task.
If a scheduled task encounters an issue, that is the caller responsibility to re-schedule or not the task according to the issue cause.
Each layer has its its responsibilities. Keeping these make code both clear and maintainable.


ScheduledFuture.get() : the right API to catch exceptions and errors occurred in the task

ScheduledExecutorService.scheduleWithFixedDelay()/scheduleAtFixRate() state in their specification :

If any execution of the task encounters an exception, subsequent
executions are suppressed. Otherwise, the task will only terminate via
cancellation or termination of the executor.

It means that ScheduledFuture.get() doesn't return at each scheduled invocation but that it returns for the last invocation of the task, that is a task cancelation : caused by ScheduledFuture.cancel() or a exception thrown in the task.
So handling the ScheduledFuture return to capture the exception with ScheduledFuture.get() looks right :

  try {
    future.get();

  } catch (InterruptedException e) {
    // ... to handle
  } catch (ExecutionException e) {
    // ... and unwrap the exception OR the error that caused the issue
    Throwable cause = e.getCause();       
  }

Example with the default behavior : halting the scheduling if one of the task execution encounters an issue

It executes a task that for the third executions thrown an exception and terminates the scheduling.
In some scenarios, we want that.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    Future<?> futureA = executor
        .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
    try {
      System.out.println("before get()");
      futureA.get(); // will return only if canceled
      System.out.println("after get()");
    } catch (InterruptedException e) {
      // handle that : halt or no
    } catch (ExecutionException e) {
      System.out.println("exception caught :" + e.getCause());
    }

    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");
      if (invocationDone.decrementAndGet() == 0) {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

Output :

before get()
pool-1-thread-1, execution
pool-1-thread-1, execution
pool-1-thread-1, execution
exception caught :java.lang.IllegalArgumentException: ohhh an Exception in MyRunnable

Example with the possibility to go on the scheduling if one of the task execution encounters an issue

It executes a task that throws an exception at the two first executions and throws an error at the third one.
We can see that the client of the tasks can choose to halt or not the scheduling : here I go on in cases of exception and I stop in case of error.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    boolean mustHalt = true;
    do {
      Future<?> futureA = executor
              .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
      try {
        futureA.get(); // will return only if canceled
      } catch (InterruptedException e) {
        // handle that : halt or not halt
      } catch (ExecutionException e) {
        if (e.getCause() instanceof Error) {
          System.out.println("I halt in case of Error");
          mustHalt = true;
        } else {
          System.out.println("I reschedule in case of Exception");
          mustHalt = false;
        }
      }
    }
    while (!mustHalt);
    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");

      if (invocationDone.decrementAndGet() == 0) {
        throw new Error("ohhh an Error in MyRunnable");
      } else {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

Output :

pool-1-thread-1, execution
I reschedule in case of Exception
pool-1-thread-1, execution
I reschedule in case of Exception
pool-1-thread-2, execution
I halt in case of Error
云归处 2024-12-04 09:20:31

另一种解决方案是在 Runnable 中吞掉异常。您可以使用方便的 VerboseRunnable 来自 jcabi-log 的类,例如:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // do business logic, may Exception occurs
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 10, TimeUnit.SECONDS
);

Another solution would be to swallow an exception in the Runnable. You can use a convenient VerboseRunnable class from jcabi-log, for example:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // do business logic, may Exception occurs
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 10, TimeUnit.SECONDS
);
十雾 2024-12-04 09:20:31

我知道这是一个老问题,但是如果有人将延迟的 CompletableFuture 与 ScheduledExecutorService 一起使用,那么应该以这种方式处理这个问题:

private static CompletableFuture<String> delayed(Duration delay) {
    CompletableFuture<String> delayed = new CompletableFuture<>();
    executor.schedule(() -> {
        String value = null;
        try {
            value = mayThrowExceptionOrValue();
        } catch (Throwable ex) {
            delayed.completeExceptionally(ex);
        }
        if (!delayed.isCompletedExceptionally()) {
            delayed.complete(value);
        }
    }, delay.toMillis(), TimeUnit.MILLISECONDS);
    return delayed;
}

并在 CompletableFuture 中处理异常>:

CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
delayed.exceptionally(ex -> {
    //handle exception
    return null;
}).thenAccept(value -> {
    //handle value
});

I know that this is old question, but if somebody is using delayed CompletableFuture with ScheduledExecutorService then should handle this in that way:

private static CompletableFuture<String> delayed(Duration delay) {
    CompletableFuture<String> delayed = new CompletableFuture<>();
    executor.schedule(() -> {
        String value = null;
        try {
            value = mayThrowExceptionOrValue();
        } catch (Throwable ex) {
            delayed.completeExceptionally(ex);
        }
        if (!delayed.isCompletedExceptionally()) {
            delayed.complete(value);
        }
    }, delay.toMillis(), TimeUnit.MILLISECONDS);
    return delayed;
}

and handling exception in CompletableFuture:

CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
delayed.exceptionally(ex -> {
    //handle exception
    return null;
}).thenAccept(value -> {
    //handle value
});
只等公子 2024-12-04 09:20:31

捕获异常并使计划任务保持活动状态的巧妙方法。

首先,定义一个函数式接口。

    @FunctionalInterface
    interface NoSuppressedRunnable extends Runnable {

        @Override
        default void run() {
            try {
                doRun();
            } catch (Exception e) {
                log.error("...", e);
            }
        }


        void doRun();

    }

然后,像这样提交工作。

executorService.scheduleAtFixedRate((NoSuppressedRunnable) () -> {
    // Complier implies that this is an implement of doRun() once you put the cast above
}, 0, 60L, TimeUnit.SECONDS);

An elegent way to catch the exception and keep scheduled tasks alive.

First, define a functional interface.

    @FunctionalInterface
    interface NoSuppressedRunnable extends Runnable {

        @Override
        default void run() {
            try {
                doRun();
            } catch (Exception e) {
                log.error("...", e);
            }
        }


        void doRun();

    }

Then, commit the job like this.

executorService.scheduleAtFixedRate((NoSuppressedRunnable) () -> {
    // Complier implies that this is an implement of doRun() once you put the cast above
}, 0, 60L, TimeUnit.SECONDS);
南巷近海 2024-12-04 09:20:31

受 @MBec 解决方案的启发,我为 ScheduledExecutorService 编写了一个很好的通用包装器:

  • 将捕获并打印任何未处理的抛出异常。
  • 将返回 Java 8 CompletableFuture 而不是 Future。

:)

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * This class use as a wrapper for the Native Java ScheduledExecutorService class.
 * It was created in order to address the very unpleasant scenario of silent death!
 * explanation: each time an unhandled exception get thrown from a running task that runs by ScheduledExecutorService
 * the thread will die and the exception will die with it (nothing will propagate back to the main thread).
 *
 * However, HonestScheduledExecutorService will gracefully print the thrown exception with a custom/default message,
 * and will also return a Java 8 compliant CompletableFuture for your convenience :)
 */
@Slf4j
public class HonestScheduledExecutorService {

    private final ScheduledExecutorService scheduledExecutorService;
    private static final String DEFAULT_FAILURE_MSG = "Failure occurred when running scheduled task.";

    HonestScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public CompletableFuture<Object> scheduleWithFixedDelay(Callable callable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleWithFixedDelay(Runnable runnable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> schedule(Callable callable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> schedule(Runnable runnable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> scheduleAtFixedRate(Callable callable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleAtFixedRate(Runnable runnable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Object> execute(Callable callable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public CompletableFuture<Void> execute(Runnable runnable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return scheduledExecutorService.awaitTermination(timeout, unit);
    }

    public List<Runnable> shutdownNow() {
        return scheduledExecutorService.shutdownNow();
    }

    public void shutdown() {
        scheduledExecutorService.shutdown();
    }

}

Inspired by @MBec solution, I wrote a nice generic wrapper for the ScheduledExecutorService that:

  • will catch and print any unhandled thrown exception.
  • will return a Java 8 CompletableFuture instead of a Future.

:)

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * This class use as a wrapper for the Native Java ScheduledExecutorService class.
 * It was created in order to address the very unpleasant scenario of silent death!
 * explanation: each time an unhandled exception get thrown from a running task that runs by ScheduledExecutorService
 * the thread will die and the exception will die with it (nothing will propagate back to the main thread).
 *
 * However, HonestScheduledExecutorService will gracefully print the thrown exception with a custom/default message,
 * and will also return a Java 8 compliant CompletableFuture for your convenience :)
 */
@Slf4j
public class HonestScheduledExecutorService {

    private final ScheduledExecutorService scheduledExecutorService;
    private static final String DEFAULT_FAILURE_MSG = "Failure occurred when running scheduled task.";

    HonestScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public CompletableFuture<Object> scheduleWithFixedDelay(Callable callable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleWithFixedDelay(Runnable runnable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> schedule(Callable callable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> schedule(Runnable runnable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> scheduleAtFixedRate(Callable callable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleAtFixedRate(Runnable runnable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Object> execute(Callable callable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public CompletableFuture<Void> execute(Runnable runnable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return scheduledExecutorService.awaitTermination(timeout, unit);
    }

    public List<Runnable> shutdownNow() {
        return scheduledExecutorService.shutdownNow();
    }

    public void shutdown() {
        scheduledExecutorService.shutdown();
    }

}
破晓 2024-12-04 09:20:31

传递给 (ScheduledExecutorService) 的线程的 run() 中的任何异常都不会被抛出,如果我们使用 future.get() 来获取状态,那么主线程将无限等待

Any exception in the run() of a thread which is passed to (ScheduledExecutorService) is never thrown out and if we use future.get() to get status, then the main thread waits infinitely

自此以后,行同陌路 2024-12-04 09:20:31

就我个人而言,我不同意这里的所有答案。它们的主要问题是它们以奇怪的方式提供相同的解决方案。相反,您应该做的是创建自己的线程工厂,在正在创建的线程上安装未捕获的异常处理程序。例如,这是安装到任何可以自行创建线程的执行程序中的 DefaultThreadFactory。遗憾的是,从 Java 11 开始它仍然是一个私有类,因为我想扩展它而不是将它复制到我的代码库中。下面是它在 Executors.java 文件中的显示方式的片段。

    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

正如您所看到的,该接口本身是一个处理创建新线程的单一方法。除了找出创建线程工厂的线程组之外,它没有什么魔力。有趣的是,线程是作为非守护进程创建的。

创建线程时,您可以调用 setThreadUncaughtExceptionHandler ,它接受一个处理程序,您应该在其中处理该线程中发生的任何未捕获的异常。默认情况下,它将从您的线程组继承,该线程组具有以下内容

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

默认情况下,它将尝试将处理委托给父线程组(如果存在),然后才测试平台默认未捕获的异常处理程序。通常它没有显式安装。如果您想对不了解这一点的不良代码库造成一些真正的损害,您可以通过 Thread#setDefaultUncaughtExceptionHandler 安装一个。不用担心,如果运行时具有安全管理器,您将无法执行此操作。

如果您要安装自己的处理程序,则将调用该处理程序而不是第一组。

现在,解决这个问题,回答你的问题:如何处理执行器中的异常。默认情况下,如果代码无法处理自己的错误,则线程被视为死亡。我认为你应该坚持这一点。未捕获的异常处理程序不会保存您的线程。相反,它将帮助您诊断发生了什么。为了进入 ScheduledExecutor 实现(允许定期执行可运行对象),适用相同的规则:如果一次执行失败,则该线程以及应该运行的可运行对象将被终止。

简而言之,处理你自己的错误。我们检查异常是有原因的。

但是未经检查的异常呢?

有趣的是,因为我会犯与其他发帖者相同的错误:在 Throwable 上使用 try/catch,但断言它不是 ThreadDeath 错误。如果你确实得到了一个,你必须重新抛出它以确保线程确实死亡。

Personally, I disagree with all the answers here. The main issue with all of them is they provide the same solution in weird flavors. Instead, what you should be doing is creating your own thread factory that installs an uncaught exception handler on the thread that is being created. For example, this is the DefaultThreadFactory that is installed into any executor that would create threads on its own. Shamefully, it's still a private class as of Java 11, since I would like to extend it instead of copying it into my codebase. Below is a snippet how it appears in Executors.java file.

    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

As you can see, the interface itself is a single method that handles creating new threads. There isn't much magic to it besides figuring out the thread group where is the thread factory created in. The interesting bit is that threads are created as non-daemon.

When the thread is created, you can call setThreadUncaughtExceptionHandler which accepts a handler where you should be handling any uncaught exceptions that had happened in that thread. By default, it will be inherited from your thread group, which has the following

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

By default, it will attempt to delegate handling to parent thread group if it exists, and only then test for platform default uncaught exception handler. Usually it is not explicitly installed. If you want to do some real damage to poor codebases that are not aware of this, you can install one via Thread#setDefaultUncaughtExceptionHandler. Don't worry, you won't get to do that if the runtime has Security manager in place.

If you were to install your own handler, that handler will be called instead of the group one.

Now with that out of the way, to your question: How do you handle exceptions in Executors. By default, a thread is considered dead if code is unable to handle its own errors. And I think you should adhere to that. Uncaught exception handler won't save your thread. Instead it will help you diagnose what happened. To segway into ScheduledExecutor implementations, which permit periodic execution of a runnable, the same rules apply: if one execution fails, the thread is killed, along with the runnable that was supposed to get run.

In short, handle your own errors. We have checked exceptions for a reason.

But what about unchecked exceptions?

Funny, since I will commit the same sin as other posters do: use try/catch on Throwable, but assert that it's not a ThreadDeath error. If you do get one, you must rethrow it to ensure the thread actually does die.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文