【问题标题】:ExecutorService that cancels current task when new ones are submittedExecutorService 在提交新任务时取消当前任务
【发布时间】:2012-10-19 03:12:14
【问题描述】:

考虑一个接受需要很长时间初始化的服务的配置设置的用户界面(例如,JDBC 连接的参数)。我们希望我们的用户界面在服务初始化发生时保持响应。如果用户进行其他更改,则应取消初始化并使用新参数重新启动。

由于在用户键入每个字符时会在配置中包含参数,因此可能会连续创建多个初始化请求。只应执行最后一个。

我们已经编写了实现此结果的代码,但似乎这种行为非常适合作为 ExecutorService 实现。在我们将所有内容重构为 ExecutorService 之前,我想我会问一下世界上是否已经有类似的实现。

更具体一点:

ExecutorService 将有一个工作线程。一旦提交了新任务,当前任务就会被取消(并且工作人员会中断)。然后捕获新任务以供下一次执行。如果提交了另一个任务,则再次取消当前任务,并将“下一次执行”任务设置为这个新任务。当工作线程最终选择下一个任务执行时,它总是最后一个提交的任务——所有其他任务要么被取消,要么被丢弃。

有没有人愿意分享这样的实现?或者是否有一个涵盖这种行为的标准库?实现起来并不难,但确定线程安全可能会很棘手,所以如果可以的话,我宁愿使用经过验证的代码。

【问题讨论】:

    标签: java executorservice


    【解决方案1】:

    这是我最终想到的——我对任何 cmets 都感兴趣:

    public class InterruptingExecutorService extends ThreadPoolExecutor{
        private volatile FutureTask<?> currentFuture;
    
        public InterruptingExecutorService(boolean daemon) {
            super(0, 1, 1000L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(), 
                    daemon ? new DaemonThreadFactory() : Executors.defaultThreadFactory());
    
        }
    
        public static class DaemonThreadFactory implements ThreadFactory{
            ThreadFactory delegate = Executors.defaultThreadFactory();
    
            @Override
            public Thread newThread(Runnable r) {
                Thread t = delegate.newThread(r);
                t.setDaemon(true);
                return t;
            }
    
        }
    
        private void cancelCurrentFuture(){
            // cancel all pending tasks
            Iterator<Runnable> it = getQueue().iterator();
            while(it.hasNext()){
                FutureTask<?> task = (FutureTask<?>)it.next();
                task.cancel(true);
                it.remove();
            }
    
            // cancel the current task
            FutureTask<?> currentFuture = this.currentFuture;
            if(currentFuture != null){
                currentFuture.cancel(true);
            }
        }
    
        @Override
        public void execute(Runnable command) {
            if (command == null) throw new NullPointerException();
    
            cancelCurrentFuture();
            if (!(command instanceof FutureTask)){ // we have to be able to cancel a task, so we have to wrap any non Future
                command = newTaskFor(command, null);
            }
            super.execute(command);
        }
    
        @Override
        protected void beforeExecute(Thread t, Runnable r) {
            // it is safe to access currentFuture like this b/c we have limited the # of worker threads to only 1
            // it isn't possible for currentFuture to be set by any other thread than the one calling this method
            this.currentFuture = (FutureTask<?>)r;
        }
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            // it is safe to access currentFuture like this b/c we have limited the # of worker threads to only 1
            // it isn't possible for currentFuture to be set by any other thread than the one calling this method
            this.currentFuture = null;
        }
    }
    

    【讨论】:

    • 很酷,以更好的方式制作了 interruptWorkers。 getQueue() 中的项目可以转换为 FutureTask 是问题的关键
    • 很有趣,我发现 execute() 方法妨碍了该演员。所以我结束了一个不同的实现,它覆盖了执行,检查是否有一个 FutureTask 被传入,如果没有,则包装它。最终的代码实际上更干净,并且无论如何使用 ExecutorService 都可以工作 - 如果我记得,我会在我的代码方便时发布更新。顺便说一句,最终结果非常巧妙 - 各种用例。
    • 在扩展 AbstractExecutorService 的类上调用 submit() 会立即调用此处覆盖的方法 execute(),因此 getQueue() 将始终返回空队列。我认为您可以删除试图清除它的代码。
    【解决方案2】:

    您可能需要将 DiscardOldestPolicy 添加到您的执行程序

    http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.DiscardOldestPolicy.html

    You will get
    0 submitted
    1 submitted
    2 submitted
    3 submitted
    4 submitted
    5 submitted
    6 submitted
    7 submitted
    8 submitted
    9 submitted
    9 finished
    
    public static void main(String[] args) throws SecurityException,
            NoSuchMethodException {
    
        final Method interruptWorkers = ThreadPoolExecutor.class
                .getDeclaredMethod("interruptWorkers");
        interruptWorkers.setAccessible(true);
    
        ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L,
                TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),
                new RejectedExecutionHandler() {
    
                    @Override
                    public void rejectedExecution(Runnable r,
                            ThreadPoolExecutor executor) {
                        if (!executor.isShutdown()) {
                            try {
    
                                interruptWorkers.invoke(executor);
                                executor.execute(r);
    
                            } catch (IllegalArgumentException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
    
    
        for(int i =0 ;i<10;i++)
            executor.submit(newTask(i));
    }
    
    private static Runnable newTask(final int id) {
        return new Runnable() {
            {
    
                System.out.println(id + " submitted");
            }
    
            @Override
            public void run() {
                try {
    
                    Thread.sleep(5000l);
                    System.out.println(id + " finished");
                } catch (InterruptedException e) {
                }
    
            }
    
        };
    }
    

    【讨论】:

    • 啊 - 非常优雅...不得不使用 interruptWorkers 方法有点令人不安,但这确实是这里最好的方法。
    • 好的 - interruptWorkers 不是我正在运行的 JDK 的一部分(Oracle,1.6.0_34),所以这不起作用。关于可以使用公共 API 的方法的其他想法?
    • 我正在使用 openjdk 1.6.0_24 并从源代码获取
    • private void interruptWorkers() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { try { w.thread.interrupt(); } catch (SecurityException 忽略) { } } } 最后 { mainLock.unlock(); } }
    • 是的 - 它不在 1.6.0_34 中(我认为这强调了尝试使用私有成员函数的危险)。我还有一些进一步的担忧,即使这确实有效,它也会有效地阻塞输入操作,因为 interruptWorkers/executor.execute(r) 调用会被反复调用,直到 worker 实际释放。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多