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是否需要杀死线程池,因人而异。如果不杀死的话,那超时的任务会继续执行。
题外话:如果你有好的解决方式,欢迎和题主探讨。谢谢。
https://github.com/godmaybelieve