一、背景
題主最近遇到一個問題,本來通過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是否需要殺死線程池,因人而異。如果不殺死的話,那超時的任務會繼續執行。
題外話:如果你有好的解決方式,歡迎和題主探討。謝謝。