简短概述
任何从您的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的调用,也没有更多的工作。这种工作停顿是悄无声息的,你不会得到通知。这篇恶意语言的blog posting生动地叙述了学习这种行为的艰难之路。
解决方案
answer by yegor256和answer by arun_suresh的答案似乎基本上是正确的。这些答案存在两个问题:
错误和异常?
在Java中,我们通常只捕获
exceptions,而不是
errors。但是,在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 ) {
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 ) {
logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
}
}
};
在每个Runnable/Callable中
无论是 ScheduledExecutorService
,对于任何一个 Runnable
的 run
方法,我觉得始终使用通用的 try-catch( Exception† e )
是明智的。对于任何一个 Callable
的 call
方法同样适用。
完整示例代码
在实际工作中,我通常会单独定义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;
public class App {
public static void main ( String[] args ) {
App app = new App();
app.doIt();
}
private void doIt () {
System.out.println( "BASIL - Start." );
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture < ? > handle =
scheduler.scheduleWithFixedDelay( new Runnable() {
public void run () {
try {
System.out.println( "Now: " + ZonedDateTime.now( ZoneId.systemDefault() ) );
} catch ( Exception e ) {
}
}
} , 0 , 2 , TimeUnit.SECONDS );
try {
Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) );
} catch ( InterruptedException e ) {
e.printStackTrace();
}
scheduler.shutdown();
System.out.println( "BASIL - Done." );
}
}
运行时。
现在时间: 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[美国/洛杉矶]
另一个例子
这是另一个例子。我们的任务需要运行大约20次,每5秒运行一次,持续1分钟。但在第五次运行时,我们会抛出异常。
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() );
}
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
对象。
Throwable
而不是Exception
:我最近遇到了一个第三方库的问题,这个库对服务进行定期健康检查。该库在内部使用了ScheduledExecutorService
。我一直从健康检查中收到超时的错误。我阅读了相关的博客文章后,发现调度程序可能会吞噬错误。因此,我添加了一个try-catch
块来捕获Exception
,但它没有起作用。经过一周左右的调查后,我发现原来是一个依赖项的问题导致了一个NoSuchMethodError
错误,而这是一个Error
类型。 - jannisrun
方法中使用try-catch
。”(C) 我添加了另一个代码示例到答案中,以演示抑制抛出的异常。 - Basil Bourquecatch (Exception e) {}
来处理异常。我从未这样说过。我说的是,从计划执行器服务中冒出的异常不会导致应用程序崩溃。因此,在这种情况下,“让应用程序崩溃”这种方法行不通。我甚至给了你一个代码示例来证明我的观点。试一试;我已经尝试并在发布之前编写了它。试试看——你的应用程序不会**崩溃。 - Basil Bourque