ScheduledExecutorService異常處理的正確姿勢

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/。

晚安晚安。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容