ExecutorService在超时后中断任务

Edw*_*ale 87 java concurrency multithreading executorservice

我正在寻找一个可以提供超时的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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*int 84

您可以使用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);
Run Code Online (Sandbox Code Playgroud)

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

  • 有趣的想法,但如果任务在超时之前完成(通常会这样)会怎么样?我宁愿没有大量的清理任务等待运行,只是为了找出已分配的任务已经完成.当他们完成删除他们的清理任务时,需要另一个线程来监控Futures. (11认同)
  • @John W.:我刚刚意识到你实施的另一个问题.正如我之前评论的那样,我需要在任务开始执行时开始超时.我认为唯一的方法是使用`beforeExecute`钩子. (5认同)
  • 遗嘱执行人只会安排一次取消.如果任务完成,那么取消是无操作,工作继续不变.只需要一个额外的线程计划来取消任务,一个线程来运行它们.您可以有两个执行程序,一个用于提交主要任务,另一个用于取消它们. (3认同)
  • 这是真的,但是如果超时为5小时并且在那个时间内执行10k任务会怎样.我想避免让所有那些无操作的人占用内存并导致上下文切换. (3认同)

Fla*_*vio 6

不幸的是,解决方案存在缺陷.ScheduledThreadPoolExecutor这个问题中也报告了一种错误:取消提交的任务并不能完全释放与任务相关的内存资源; 只有在任务到期时才会释放资源.

因此,如果您创建一个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序耗尽了可用内存,尽管它等待生成Runnable的内存完成.

我虽然有一段时间,但不幸的是我无法想出一个好的解决方案.

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


ZZ *_*der 5

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

java本机进程超时