ScheduledExecutorService(下文簡稱SES)是j.u.c包中提供任務定時調(diào)度方法的接口,它的主要實現(xiàn)類就是調(diào)度線程池ScheduledThreadPoolExecutor,應用非常廣泛,在我的上一篇拙作《修改ES IK插件源碼,配合MySQL實現(xiàn)詞庫熱更新》中就用到了它。
但特別需要注意,一旦SES執(zhí)行的邏輯中拋出異常,那么調(diào)度會自動停止,并且不會有任何提示信息。在其scheduleWithFixedDelay()和scheduleAtFixedRate()方法的JavaDoc中,都有這樣的描述:
If any execution of the task encounters an exception, subsequent executions are suppressed.
看了之后,不禁想對JUC世界的王Doug Lea說:

舉個栗子吧。
public class SESExample {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
Random random = new Random();
pool.scheduleWithFixedDelay(() -> {
int i = random.nextInt(100);
System.out.println(i);
if (i % 5 == 0) {
throw new RuntimeException("Exception triggered! HAHA");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
輸出如下:

隨機到90的時候拋出了RuntimeException,并且確實沒有任何報錯。要解決這個令人窒息的問題,有以下兩種方法。
- try-catch大法好,永遠都要檢查異常
pool.scheduleWithFixedDelay(() -> {
try {
int i = random.nextInt(100);
System.out.println(i);
if (i % 5 == 0) {
throw new RuntimeException("Exception triggered! HAHA");
}
} catch (Throwable t) {
t.printStackTrace();
}
}, 1, 3, TimeUnit.SECONDS);
- 利用調(diào)度方法返回的ScheduledFuture對象
Runnable runnable = () -> {
int i = random.nextInt(100);
System.out.println(i);
if (i % 5 == 0) {
throw new RuntimeException("Exception triggered! HAHA");
}
};
ScheduledFuture future = pool.scheduleWithFixedDelay(runnable, 1, 3, TimeUnit.SECONDS);
try {
future.get();
} catch (InterruptedException | ExecutionException t) {
t.printStackTrace();
future = pool.scheduleWithFixedDelay(runnable, 1, 3, TimeUnit.SECONDS);
}
這兩種方法都可以保證異常被捕獲后繼續(xù)調(diào)度,不會終止。但是如下這種方法呢?
ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler((t, e) -> {
e.printStackTrace();
});
return thread;
});
Random random = new Random();
pool.scheduleWithFixedDelay(() -> {
int i = random.nextInt(100);
System.out.println(i);
if (i % 5 == 0) {
throw new RuntimeException("Exception triggered! HAHA");
}
}, 1, 3, TimeUnit.SECONDS);
由實際測試可以得知,通過線程工廠ThreadFactory設置異常處理器UncaughtExceptionHandler是沒有作用的,也就是說SES遇到異常時根本不會調(diào)用uncaughtException()方法,所以還是老老實實采用以上兩種方法之一吧。
多嘴一句,關(guān)于這個問題,有一位國外的暴躁程序員在很久之前就寫過,并且情緒十分激動,通篇傳統(tǒng)美德。為了避嫌,只給出傳送門,奇文共賞吧:http://code.nomad-labs.com/2011/12/09/mother-fk-the-scheduledexecutorservice/。
晚安晚安。