代码之家  ›  专栏  ›  技术社区  ›  Edward Dale

在超时后中断任务的ExecutorService

  •  81
  • Edward Dale  · 技术社区  · 15 年前

    我在找一个 ExecutorService 可以提供超时的实现。如果提交给ExecutorService的任务的运行时间长于超时时间,则会中断这些任务。实现这样一个野兽并不是一个困难的任务,但我想知道是否有人知道现有的实现。

    以下是我根据下面的一些讨论得出的结论。有什么意见吗?

    import java.util.List;
    import java.util.concurrent.*;
    
    public class TimeoutThreadPoolExecutor extends ThreadPoolExecutor {
        private final long timeout;
        private final TimeUnit timeoutUnit;
    
        private final ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
        private final ConcurrentMap<Runnable, ScheduledFuture> runningTasks = new ConcurrentHashMap<Runnable, ScheduledFuture>();
    
        public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, long timeout, TimeUnit timeoutUnit) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
            this.timeout = timeout;
            this.timeoutUnit = timeoutUnit;
        }
    
        public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, long timeout, TimeUnit timeoutUnit) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
            this.timeout = timeout;
            this.timeoutUnit = timeoutUnit;
        }
    
        public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
            this.timeout = timeout;
            this.timeoutUnit = timeoutUnit;
        }
    
        public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
            this.timeout = timeout;
            this.timeoutUnit = timeoutUnit;
        }
    
        @Override
        public void shutdown() {
            timeoutExecutor.shutdown();
            super.shutdown();
        }
    
        @Override
        public List<Runnable> shutdownNow() {
            timeoutExecutor.shutdownNow();
            return super.shutdownNow();
        }
    
        @Override
        protected void beforeExecute(Thread t, Runnable r) {
            if(timeout > 0) {
                final ScheduledFuture<?> scheduled = timeoutExecutor.schedule(new TimeoutTask(t), timeout, timeoutUnit);
                runningTasks.put(r, scheduled);
            }
        }
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            ScheduledFuture timeoutTask = runningTasks.remove(r);
            if(timeoutTask != null) {
                timeoutTask.cancel(false);
            }
        }
    
        class TimeoutTask implements Runnable {
            private final Thread thread;
    
            public TimeoutTask(Thread thread) {
                this.thread = thread;
            }
    
            @Override
            public void run() {
                thread.interrupt();
            }
        }
    }
    
    8 回复  |  直到 8 年前
        1
  •  76
  •   Reaz Murshed vir us    8 年前

    您可以使用 ScheduledExecutorService 为了这个。首先,您只需提交一次即可立即开始并保留创建的未来。之后,您可以提交一个新任务,该任务将在一段时间后取消保留的未来任务。

     ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 
     final Future handler = executor.submit(new Callable(){ ... });
     executor.schedule(new Runnable(){
         public void run(){
             handler.cancel();
         }      
     }, 10000, TimeUnit.MILLISECONDS);
    

    这将执行处理程序(主要功能将被中断)10秒,然后取消(即中断)该特定任务。

        2
  •  5
  •   Community CDub    8 年前

    不幸的是,解决方案有缺陷。有一种虫子 ScheduledThreadPoolExecutor ,也在 this question :取消已提交的任务不会完全释放与该任务关联的内存资源;只有在任务过期时才释放资源。

    如果您因此创建 TimeoutThreadPoolExecutor 有了相当长的过期时间(一种典型的用法),并且提交任务的速度足够快,最终会填满内存——即使任务实际上已经成功完成。

    您可以看到以下(非常粗糙)测试程序的问题:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = new TimeoutThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, 
                new LinkedBlockingQueue<Runnable>(), 10, TimeUnit.MINUTES);
        //ExecutorService service = Executors.newFixedThreadPool(1);
        try {
            final AtomicInteger counter = new AtomicInteger();
            for (long i = 0; i < 10000000; i++) {
                service.submit(new Runnable() {
                    @Override
                    public void run() {
                        counter.incrementAndGet();
                    }
                });
                if (i % 10000 == 0) {
                    System.out.println(i + "/" + counter.get());
                    while (i > counter.get()) {
                        Thread.sleep(10);
                    }
                }
            }
        } finally {
            service.shutdown();
        }
    }
    

    程序会耗尽可用内存,尽管它会等待生成的 Runnable s完成。

    我想了一会儿,但不幸的是,我想不出一个好的解决办法。

    编辑: 我发现这个问题被报道为 JDK bug 6602600 ,并且最近似乎已修复。

        3
  •  4
  •   Community CDub    8 年前

    在FutureTask中包装任务,您可以为FutureTask指定超时。看看我回答这个问题的例子,

    java native Process timeout

        4
  •  1
  •   Giovanni Botta    12 年前

    用这个怎么样 ExecutorService.shutDownNow() 方法如中所述 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html ?这似乎是最简单的解决方案。

        5
  •  1
  •   Maarkoize    11 年前

    似乎问题不在JDK bug 6602600中(它在2010-05-22中解决了),而是在 循环中错误的睡眠呼叫(10)。另外请注意,主螺纹必须 通过在中调用sleep(0),直接有机会让其他线程实现更高的任务。 外圆的每一个分支。 我认为最好使用thread.yield()而不是thread.sleep(0)

    以前问题代码的结果更正部分如下:

    .......................
    ........................
    Thread.yield();         
    
    if (i % 1000== 0) {
    System.out.println(i + "/" + counter.get()+ "/"+service.toString());
    }
    
    //                
    //                while (i > counter.get()) {
    //                    Thread.sleep(10);
    //                } 
    

    它可以正常工作,外计数器的数量高达1500000个测试圆。

        6
  •  1
  •   Johnny    9 年前

    经过无数次的调查,
    最后,我用 invokeAll 方法 ExecutorService 解决这个问题。
    这将在任务运行时严格中断任务。
    下面是例子

    ExecutorService executorService = Executors.newCachedThreadPool();
    
    try {
        List<Callable<Object>> callables = new ArrayList<>();
        // Add your long time task (callable)
        callables.add(new VaryLongTimeTask());
        // Assign tasks for specific execution timeout (e.g. 2 sec)
        List<Future<Object>> futures = executorService.invokeAll(callables, 2000, TimeUnit.MILLISECONDS);
        for (Future<Object> future : futures) {
            // Getting result
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
    executorService.shutdown();
    

    专业是你也可以提交 ListenableFuture 同时 遗嘱执行人服务
    只需稍微更改代码的第一行。

    ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
    

    ListeningExecutorService 是的听力特征 遗嘱执行人服务 谷歌瓜娃项目( 网址:com.google.guava )

        7
  •  1
  •   amanteaux    8 年前

    使用john w-answer,我创建了一个实现,它在任务开始执行时正确地开始超时。我甚至为它编写了一个单元测试:)

    但是,它不适合我的需要,因为某些IO操作不会在 Future.cancel() 被称为 Thread.interrupted() 被调用)。

    不管怎样,如果有人感兴趣,我创建了一个要点: https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf

        8
  •  0
  •   Ionut Mesaros    10 年前

    这个备选方案怎么样:

    • 两人有两名执行人:
      • 一个用于:
        • 提交任务,不考虑任务超时
        • 添加未来结果和应结束到内部结构的时间
      • 一种用于执行内部作业的方法,它检查内部结构,以确定某些任务是否超时,以及是否必须取消这些任务。

    小样本在这里:

    public class AlternativeExecutorService 
    {
    
    private final CopyOnWriteArrayList<ListenableFutureTask> futureQueue       = new CopyOnWriteArrayList();
    private final ScheduledThreadPoolExecutor                scheduledExecutor = new ScheduledThreadPoolExecutor(1); // used for internal cleaning job
    private final ListeningExecutorService                   threadExecutor    = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5)); // used for
    private ScheduledFuture scheduledFuture;
    private static final long INTERNAL_JOB_CLEANUP_FREQUENCY = 1000L;
    
    public AlternativeExecutorService()
    {
        scheduledFuture = scheduledExecutor.scheduleAtFixedRate(new TimeoutManagerJob(), 0, INTERNAL_JOB_CLEANUP_FREQUENCY, TimeUnit.MILLISECONDS);
    }
    
    public void pushTask(OwnTask task)
    {
        ListenableFuture<Void> future = threadExecutor.submit(task);  // -> create your Callable
        futureQueue.add(new ListenableFutureTask(future, task, getCurrentMillisecondsTime())); // -> store the time when the task should end
    }
    
    public void shutdownInternalScheduledExecutor()
    {
        scheduledFuture.cancel(true);
        scheduledExecutor.shutdownNow();
    }
    
    long getCurrentMillisecondsTime()
    {
        return Calendar.getInstance().get(Calendar.MILLISECOND);
    }
    
    class ListenableFutureTask
    {
        private final ListenableFuture<Void> future;
        private final OwnTask                task;
        private final long                   milliSecEndTime;
    
        private ListenableFutureTask(ListenableFuture<Void> future, OwnTask task, long milliSecStartTime)
        {
            this.future = future;
            this.task = task;
            this.milliSecEndTime = milliSecStartTime + task.getTimeUnit().convert(task.getTimeoutDuration(), TimeUnit.MILLISECONDS);
        }
    
        ListenableFuture<Void> getFuture()
        {
            return future;
        }
    
        OwnTask getTask()
        {
            return task;
        }
    
        long getMilliSecEndTime()
        {
            return milliSecEndTime;
        }
    }
    
    class TimeoutManagerJob implements Runnable
    {
        CopyOnWriteArrayList<ListenableFutureTask> getCopyOnWriteArrayList()
        {
            return futureQueue;
        }
    
        @Override
        public void run()
        {
            long currentMileSecValue = getCurrentMillisecondsTime();
            for (ListenableFutureTask futureTask : futureQueue)
            {
                consumeFuture(futureTask, currentMileSecValue);
            }
        }
    
        private void consumeFuture(ListenableFutureTask futureTask, long currentMileSecValue)
        {
            ListenableFuture<Void> future = futureTask.getFuture();
            boolean isTimeout = futureTask.getMilliSecEndTime() >= currentMileSecValue;
            if (isTimeout)
            {
                if (!future.isDone())
                {
                    future.cancel(true);
                }
                futureQueue.remove(futureTask);
            }
        }
    }
    
    class OwnTask implements Callable<Void>
    {
        private long     timeoutDuration;
        private TimeUnit timeUnit;
    
        OwnTask(long timeoutDuration, TimeUnit timeUnit)
        {
            this.timeoutDuration = timeoutDuration;
            this.timeUnit = timeUnit;
        }
    
        @Override
        public Void call() throws Exception
        {
            // do logic
            return null;
        }
    
        public long getTimeoutDuration()
        {
            return timeoutDuration;
        }
    
        public TimeUnit getTimeUnit()
        {
            return timeUnit;
        }
    }
    }