【问题标题】:How to implement an ExecutorService to execute tasks on a rotation-basis?如何实现 ExecutorService 以轮换执行任务?
【发布时间】:2012-05-07 22:32:42
【问题描述】:

我使用java.util.concurrent.ExecutorServicefixed thread pool 来执行任务列表。我的任务列表通常在 80 到 150 个左右,并且我将随时运行的线程数限制为 10,如下所示:

ExecutorService threadPoolService = Executors.newFixedThreadPool(10);

for ( Runnable task : myTasks ) 
{     
    threadPoolService.submit(task); 
}

我的用例要求即使已完成的任务也应再次重新提交给 ExecutorService,但只有在所有已经提交的任务时才应再次执行/接受服务/完成。也就是说,基本上,提交的任务应该轮流执行。因此,在这种情况下不会有threadPoolService.shutdown()threadPoolService.shutdownNow() 调用。

我的问题是,如何实现 ExecutorService 服务于轮换任务?

【问题讨论】:

    标签: java multithreading threadpool


    【解决方案1】:

    ThreadPoolExecutor 为 afterExecution 提供了一个扩展点,您可以在其中将作业放回队列的末尾。

    public class TaskRepeatingThreadPoolExecutor extends ThreadPoolExecutor {
    
        public TaskRepeatingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            this.submit(r);
        }
    }
    

    当然,在没有 ExecutorService 方便的工厂方法帮助的情况下,您需要做更多的工作来自己实例化它,但是构造函数很简单,可以理解。

    【讨论】:

    • 感谢您的建议,让我知道ThreadPoolExecutor 类的存在。我在这里有一个问题,我需要什么/如何/为什么将BlockingQueue&lt;Runnable&gt; workQueue 传递给构造函数。在ExecutorService 的情况下,我曾经像threadPoolService.submit(task) 这样提交我的所有任务。我无法理解这个BlockingQueue&lt;Runnable&gt; workQueue。希望你能让我明白这一点。
    • 刚刚阅读了BlockingQueue&lt;Runnable&gt; 并考虑将其更新回到这里。 BlockingQueue 主要用于保存当池中的所有线程都忙于执行任务时发送给执行器的工作/任务。
    • 我不确定这种技术是否有效,afterExecute 是用 FutureTask(Runnable - Wrapper around the actual task) 调用的,它具有跟踪完成状态的内部状态。如果重新提交它只是返回而不执行实际任务。
    【解决方案2】:

    答案与ExecutorService 的实例所使用的工作队列的实现更相关。所以,我建议:

    1. 首先选择提供循环队列功能的java.util.concurrent.BlockingQueue (an example) 的实现。 注意,选择BlockingQueue 的原因是等待直到下一个任务被提供给队列;所以,在循环+阻塞队列的情况下,你应该小心如何提供相同的行为和功能。

    2. 不要使用Executors.new... 创建新的ThreadPoolExecutor,而是使用direct constructor,例如

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue&lt;Runnable&gt; workQueue)

    这样,除非您命令执行器到shutdown,否则它将尝试从队列中获取下一个任务以从其工作队列中执行,这是一个循环 任务容器。

    【讨论】:

      【解决方案3】:

      我建议以下解决方案,它完全使用标准库并发工具中存在的功能。它使用带有任务装饰器类的CyclicBarrier 和重新提交所有任务的屏障操作:

      import java.util.ArrayList;
      import java.util.List;
      import java.util.concurrent.BrokenBarrierException;
      import java.util.concurrent.CyclicBarrier;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class Rotation {
      
          private static final class RotationDecorator implements Runnable {
              private final Runnable          task;
              private final CyclicBarrier barrier;
      
      
              RotationDecorator( Runnable task, CyclicBarrier barrier ) {
                  this.task = task;
                  this.barrier = barrier;
              }
      
      
              @Override
              public void run() {
                  this.task.run();
                  try {
                      this.barrier.await();
                  } catch(InterruptedException e) {
                      ; // Consider better exception handling
                  } catch(BrokenBarrierException e) {
                      ; // Consider better exception handling
                  }
              }
          }
      
      
          public void startRotation( List<Runnable> tasks ) {
              final ExecutorService threadPoolService = Executors.newFixedThreadPool( 10 );
              final List<Runnable> rotatingTasks = new ArrayList<Runnable>( tasks.size() );
              final CyclicBarrier barrier = new CyclicBarrier( tasks.size(), new Runnable() {
                  @Override
                  public void run() {
                      Rotation.this.enqueueTasks( threadPoolService, rotatingTasks );
                  }
              } );
              for(Runnable task : tasks) {
                  rotatingTasks.add( new RotationDecorator( task, barrier ) );
              }
              this.enqueueTasks( threadPoolService, rotatingTasks );
          }
      
      
          private void enqueueTasks( ExecutorService service, List<Runnable> tasks ) {
              for(Runnable task : tasks) {
                  service.submit( task );
              }
          }
      
      }
      

      【讨论】:

        【解决方案4】:

        您可以简单地检查所有任务是否已执行,并在出现这种情况后重新提交,例如:

            List<Future> futures = new ArrayList<>();
            for (Runnable task : myTasks) {
                futures.add(threadPoolService.submit(task));
            }
            //wait until completion of all tasks
            for (Future f : futures) {
                f.get();
            }
            //restart
            ......
        

        编辑
        您似乎想在任务完成后立即重新提交。您可以使用ExecutorCompletionService,它使您能够在任务执行时检索任务, - 请参见下面的简单示例,其中有 2 个任务在完成后立即重新提交几次。示例输出:

        任务 1 提交 pool-1-thread-1
        任务 2 提交 pool-1-thread-2
        任务 1 已完成 pool-1-thread-1
        任务 1 提交 pool-1-thread-3
        任务 2 已完成 pool-1-thread-2
        任务 1 已完成 pool-1-thread-3
        任务 2 提交 pool-1-thread-4
        任务 1 提交 pool-1-thread-5
        任务 1 已完成 pool-1-thread-5
        任务 2 完成 pool-1-thread-4

        public class Test1 {
        
            public final ConcurrentMap<String, String> concurrentMap = new ConcurrentHashMap<>();
            public final AtomicInteger retries = new AtomicInteger();
            public final Object lock = new Object();
        
            public static void main(String[] args) throws InterruptedException, ExecutionException {
                int count = 0;
                List<Runnable> myTasks = new ArrayList<>();
                myTasks.add(getRunnable(1));
                myTasks.add(getRunnable(2));
                ExecutorService threadPoolService = Executors.newFixedThreadPool(10);
                CompletionService<Runnable> ecs = new ExecutorCompletionService<Runnable>(threadPoolService);
                for (Runnable task : myTasks) {
                    ecs.submit(task, task);
                }
                //wait until completion of all tasks
                while(count++ < 3) {
                    Runnable task = ecs.take().get();
                    ecs.submit(task, task);
                }
                threadPoolService.shutdown();
            }
        
            private static Runnable getRunnable(final int i) {
                return new Runnable() {
        
                    @Override
                    public void run() {
                        System.out.println("Task " + i + " submitted " + Thread.currentThread().getName() + "  ");
                        try {
                            Thread.sleep(500 * i);
                        } catch (InterruptedException ex) {
                            System.out.println("Interrupted");
                        }
                        System.out.println("Task " + i + " completed " + Thread.currentThread().getName() + "  ");
                    }
                };
            }
        }
        

        【讨论】:

        • 就我而言,我需要在完成时重新提交每个单独的任务,而不是wait until completion of all tasks。有什么想法/意见吗?
        • 酷!这就是我正在寻找的。谢谢。
        猜你喜欢
        • 1970-01-01
        • 2015-11-09
        • 2019-04-11
        • 2019-10-07
        • 1970-01-01
        • 2011-01-10
        • 2010-10-29
        • 2019-05-31
        相关资源
        最近更新 更多