当ThreadPoolExecutor没有活动工作程序时执行操作

时间:2015-01-07 09:35:39

标签: java multithreading concurrency synchronization

我有一个缓存的线程池,其中新任务以相当不可预测的方式生成。这些任务不会产生任何结果(它们是Runnables而不是Callables)。

我希望只要游泳池没有活跃的工作人员就可以执行操作

但是我不想关闭池(并且显然使用awaitTermination),因为当新任务到达时我将不得不重新初始化它(因为它可能无法预测地到达,即使在关闭期间)。

我提出了以下可能的方法:

  1. 只要生成新任务并且ThreadPoolExecutor没有活动工作人员,就会产生额外的线程(池外)。然后它应该不断检查getActiveWorkers(),直到它返回0,如果是,则执行所需的操作。

  2. 有一些线程安全队列(哪一个?),其中添加了每个新生成的任务的Future。只要队列中至少有一个条目,就会产生一个额外的线程(在池外),等待队列为空并执行所需的操作。

  3. 实现PriorityBlockingQueue以与池一起使用,并为工作线程分配比执行所需操作的线程(现在来自池内)更高的优先级。

  4. 我的问题: 我想知道是否有一些更干净的解决方案,它使用了一些不错的同步对象(如CountDownLatch,但是这里不能使用,因为我事先不知道任务的数量)?

2 个答案:

答案 0 :(得分:3)

如果我是你,我会为你的线程池实现一个装饰器,它跟踪计划的任务,并且slighlig修改运行的任务。这样,无论何时安排Runnable,您都可以安排另一个能够跟踪其自己进程的装饰Runnable

这个装饰器看起来像:

class RunnableDecorator implements Runnable {

  private final Runnable delegate;

  // this task counter must be increased on any 
  // scheduling of a task by the thread pool
  private final AtomicInteger taskCounter;

  // Constructor omitted

  @Override
  public void run() {
    try {
      delegate.run();
    } finally {
      if (taskCounter.decrementAndGet() == 0) {
        // spawn idle action
      }
    }
  }
}

当然,每当任务计划时,线程池都必须递增计数器。因此,此逻辑必须添加到Runnable,但添加到ThreadPool。最后,您可以决定是否要在同一个线程中运行 idle action ,或者如果要提供对正在执行的线程池的引用以运行新线程。如果您决定后者,请注意,空闲操作的完成将触发另一个空闲操作。但是,您也可以提供一种原始计划的方法。您还可以将装饰添加到线程队列中,但这会使提供此类原始调度变得更加困难。

这种方法是非阻塞的,并且不会过多地破坏您的代码库。请注意,胎面池在创建时不会启动操作,因此根据定义为空。

答案 1 :(得分:2)

如果您查看Executors.newCachedThreadPool()背后的来源,您可以看到它是如何使用ThreadPoolExecutor创建的。使用它,覆盖executeafterExecute方法以添加计数器。这样,递增和递减逻辑在一个位置被隔离。例如:

ExecutorService executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()) {
    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void execute(Runnable r) {
        counter.incrementAndGet();
        super.execute(r);
    }

    @Override
    public void afterExecute(Runnable r, Throwable t) {
        if (counter.decrementAndGet() == 0) {
            // thread pool is idle - do something
        }
        super.afterExecute(r, t);
    }
};