1. 程式人生 > >Java定時執行緒池停止超時任務

Java定時執行緒池停止超時任務

一、背景

題主最近遇到一個問題,本來通過ScheduledExecutorService執行緒池定時排程一個任務。奈何不知道為啥跑了2個多月,其中一個任務Hang住了,原本定時的任務則出現了問題。

關於定時執行緒池,好多人認為設定好頻率(比如1Min),它會按照這個間隔按部就班的工作。但是,如果其中一次排程任務卡住的話,不僅這次排程失敗,而且整個執行緒池也會停在這次排程上。

我們先從一個例子試著復現下問題:

public class pool {
    private static class Runner implements Runnable {
        @Override
public void run() { try { Thread.sleep(10000); System.out.println(new Date()); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(1
); service.scheduleAtFixedRate( new Runner(), 0, 1, TimeUnit.SECONDS); } }

先從Main看,啟動一個定時執行緒池,每隔1S排程一次Runner。看上去,應該是1S排程一次,但是Runner的實際執行時間為10S,那多久會排程一次?答案是10S。
所以說,這個Runner不管什麼原因掛掉了或者Hang住了,那這個定時排程執行緒池基本就廢了。

二、解決方法

那我們應該怎麼解決這個問題?如果說定時執行緒池有任務排程的超時策略就完美了,很可惜並沒有。
我們想下在併發程式設計中,哪種方式有超時策略?
對,Future

有,那我們可以結合Future,提供一種自動停止超時任務的方式,來解決某個任務Hang住的問題。

我們簡單修改下,把sleep邏輯移動到Callable中,並在Runner中使用Future來控制超時。

public class pool {
    private static class Caller implements Callable<Boolean> {
        @Override
        public Boolean call() {
            try {
                Thread.sleep(10000);
                System.out.println(new Date());
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    }

    private static class Runner implements Runnable {
        @Override
        public void run() {
            ExecutorService excutor = Executors.newSingleThreadExecutor();
            Future<Boolean> future = excutor.submit(new Caller());
            try {
                future.get(1, TimeUnit.SECONDS);
            } catch (TimeoutException e) {
                System.out.println("timeout");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                excutor.shutdownNow(); // 強制終止任務
            }
        }
    }

    public static void main(String[] args) {
        ScheduledExecutorService service
                = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(
                new Runner(), 0, 1, TimeUnit.SECONDS);
    }
}

備註:
- 實現邏輯相當於轉移了,把本來應該排程的任務交給了另外一個Future單執行緒去執行。因為存在超時邏輯,不會影響原有定時執行緒池的執行。
- finally是否需要殺死執行緒池,因人而異。如果不殺死的話,那超時的任務會繼續執行。

題外話:如果你有好的解決方式,歡迎和題主探討。謝謝。