异常处理

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

密码:

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()中的所有异常? 或任何内置的回调方法来处理异常? 谢谢!

53629 次浏览

你应该像这样使用你的 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();
}

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

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

博士

在没有通知的情况下逃脱 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 评论

捕获异常和错误的一种方法是捕获它们的超类,例如 可以扔

} catch ( Throwable t ) {

而不是..。

} catch ( Exception e ) {

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

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

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

Lambda 语法

使用 lambda (在 Java8和更高版本中)。

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

老式语法

传统的方式,在 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() );
}
}
};

在每个可运行/可调用

不管 ScheduledExecutorService是什么,对我来说,在 Runnable任何 run方法中总是使用一个通用的 try-catch( Exception† e )似乎是明智的。对于 try-catch( Exception† e )1的任何 try-catch( Exception† e )0方法也是如此。


完整的示例代码

在实际工作中,我可能会单独定义 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[ America/Los _ Angeles ]

现在: 2018-04-10T16:46:03.449178-07:00[ America/Los _ Angeles ]

现在: 2018-04-10T16:46:05.450107-07:00[ America/Los _ Angeles ]

现在: 2018-04-10T16:46:07.450586-07:00[ America/Los _ Angeles ]

现在: 2018-04-10T16:46:09.456076-07:00[ America/Los _ Angeles ]

Now: 2018-04-10T16:46:11.456872-07:00[ America/Los _ Angeles ]

现在: 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 ]

现在: 2018-04-10T16:46:19.473935-07:00[ America/Los _ Angeles ]

BASIL-搞定。

另一个例子

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

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代替 Exception来捕捉 Error物体。

我知道这是一个老问题,但是如果有人使用 ScheduledExecutorService的延迟 CompletableFuture,那么应该这样处理:

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

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

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

  • 将捕获和打印任何未处理的抛出异常。
  • 将返回 Java8 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();
}


}

老问题,但公认的答案没有给出解释,并提供了一个糟糕的例子和最支持的答案是正确的一些点,但最终鼓励您添加 catch异常在每个 Runnable.run()方法。
我不同意,因为:

  • 它不整洁: 任务捕捉自身异常的标准。
  • 它不健壮: 一个新的 Runnable 子类可能会忘记执行异常捕获和相关的故障转移。
  • 它克服了任务所提倡的低耦合性,因为它将要执行的任务与处理任务结果的方法耦合在一起。
  • 它混合了责任: 这不是处理异常或将异常传递给调用方的任务责任。任务是要执行的东西。

我认为异常传播应该由 ExecutorService框架执行,实际上它提供了这个特性。
此外,试图缩短 ExecutorService的工作方式而显得过于聪明也不是一个好主意: 框架可能会发展,您希望以标准的方式使用它。
最后,让 ExecutorService框架完成它的工作并不意味着必须停止后续的调用任务。
如果计划的任务遇到问题,那么调用方有责任根据问题原因重新安排任务或不重新安排任务。
每一层都有自己的职责,保持这些职责使代码清晰且易于维护。


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

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

如果任务的任何执行遇到异常,则后续 执行被抑制。否则,任务只能通过 取消或者终止遗嘱执行人。

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

  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

一种捕获异常并保持计划任务活动的优雅方法。

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

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

就个人而言,我不同意这里的所有答案。所有这些方法的主要问题在于,它们以奇怪的口味提供了相同的解决方案。相反,您应该做的是创建自己的线程工厂,在正在创建的线程上安装未捕获的异常处理程序。例如,这是 DefaultThreadFactory,它被安装到任何执行器中,执行器将自己创建线程。遗憾的是,从 Java11开始,它仍然是一个私有类,因为我希望扩展它,而不是将它复制到我的代码库中。下面是它在 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安装一个。别担心,如果运行时安装了安全管理器,您就不会这样做。

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

现在这个问题已经解决了,回到您的问题: 如何处理执行者中的异常。默认情况下,如果代码无法处理自己的错误,则将线程视为 死了。我认为你应该坚持这一点。未捕获的异常处理程序不会保存线程。相反,它会帮助你诊断发生了什么。为了切换到允许定期执行可运行程序的 ScheduledExecator 实现,应用了相同的规则: 如果一次执行失败,线程将与应该运行的可运行程序一起被杀死。

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

但是未检查的异常怎么办呢?

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