怎么不压倒java executorservice任务队列?

时间:2012-07-19 20:30:40

标签: java multithreading executorservice

我有以下代码片段,运行正常。但问题是它立即创建并在执行程序队列上放置了2000多个任务。

我需要检查执行程序队列中已有的任务是否完整,然后再给它更多任务。它不一定非常精确,即如果队列中剩下<10个任务,则再添加50个。

因此执行程序任务队列没有那么多挂起任务,这也将允许shutdown()及时工作,否则即使被调用,执行程序仍将首先尝试完成其队列中的所有2000个任务。 / p>

实现这一目标的最佳方法是什么?谢谢

executor = Executors.newFixedThreadPool(numThreads);

while(some_condition==true)
{
    //if(executor < 10 tasks pending)  <---- how do i do this?
    //{                             
        for(int k=0;k<20;k++)
        {  
            Runnable worker = new MyRunnable();
            executor.execute(worker);
        }
    //}
    //else 
    //{
    //      wait(3000);
    //}
} 

使用信号量更新:

private final Semaphore semaphore = new Semaphore(10)
executor = new ThreadPoolExecutorWithSemaphoreFromJohnExample();

while(some_condition==true)
{

        Runnable worker = new MyRunnable();
        //So at this point if semaphore is full, then while loop would PAUSE(??) until
        //semaphore frees up again.
          executor.execute(worker);   
} 

4 个答案:

答案 0 :(得分:8)

  

我有以下代码片段,运行正常。但问题是它立即创建并在执行程序队列上放置了2000多个任务。

执行此操作的一种方法是使用有限的作业队列创建自己的ThreadPoolExecutor,并在其上设置自定义RejectedExecutionHandler。这使您可以对要排队的作业数进行细粒度控制。

您需要自定义处理程序,因为默认情况下,如果队列已满,ThreadPoolExecutor.submit(...)将抛出RejectedExecutionException。使用下面的自定义处理程序,当它被队列拒绝时,拒绝处理程序只是将其重新插入,阻塞直到队列有空间。所以不会拒绝/放弃任何工作。

以下是关于如何启动自己的线程池并设置自己的拒绝处理程序的方法。

// you can tune the blocking queue size which is the number of jobs to queue
// when the NUM_THREADS are all working
final BlockingQueue<MyRunnable> queue =
    new ArrayBlockingQueue<MyRunnable>(NUM_JOBS_TO_QUEUE);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(NUM_THREADS, NUM_THREADS,
       0L, TimeUnit.MILLISECONDS, queue);
// by default (unfortunately) the ThreadPoolExecutor will throw an exception
// when you submit the job that fills the queue, to have it block you do:
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
   public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
      // this will block if the queue is full as opposed to throwing
      executor.getQueue().put(r);
   }
});
...
// now submit all of your jobs and it will block if the queue is full
for(int k = 0; k < 20000000; k++) {  
   Runnable worker = new MyRunnable();
   threadPool.execute(worker);
}

有关阻止线程池的详细信息,请参阅我的答案:

  

How can I make ThreadPoolExecutor command wait if there's too much data it needs to work on?

您还可以使用ThreadPoolExecutor.CallerRunsPolicy,这会导致正在提交作业的调用者进入线程池来执行作业。我不喜欢这个解决方案,因为它会阻止调用者,直到作业完成,这可能会使其他工作线程饿死。此外,如果有多个提交者,则可能仍会导致太多线程运行作业。

最后,请注意我将ThreadPoolExecutor中的核心和最大线程数设置为相同的数字。不幸的是,默认情况下,执行程序启动核心线程,然后填充队列,然后才会分配额外的线程到最大值。这完全违反直觉。

答案 1 :(得分:6)

您可以使用简单的信号量。提交获得新许可证后,在完成后释放许可证,允许其他任何等待提交的人员。

private final Semaphore semaphore = new Semaphore(10);//or however you want max queued at any given moment
ThreadPoolExecutor tp= new ThreadPoolExecutor(...){
      public void execute(Runnable r){
          semaphore.acquire();
          super.execute(r);
      }    
      public void afterExecute(Runnable r, Thread t){
         semaphore.release();  
         super.afterExecute(r,t);
      }
};

因此,如果没有更多可用许可,则提交线程将被暂停。

答案 2 :(得分:3)

我通常使用对象池队列来限制这样的系统。对于任务对象 - 在启动时填充X任务的BlockingQueue。任何想要向线程提交任务的东西都必须从池队列中获取一个,用数据加载它然后提交它。

当任务完成并导致处理后,它将被推回到池队列中以便重复使用。

如果池清空,则提交线程阻塞池队列,直到返回某些任务。

这实际上是@John Vint建议的一种信号量控制形式,但具有一些进一步的优点 - 例如,没有连续创建/ GC的可运行程序。我喜欢将PooolQueue.size转储到计时器上的GUI状态栏,这样我就可以看到忙碌的&#39;系统是,(并且还能快速检测到任何物体泄漏:)

答案 3 :(得分:2)

你最好设置一个拒绝策略,因为你不想压倒ThreadPool,我发现实现这个目标的最佳方法是不做太多复杂化就是做这样的事情:

final ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(THREADS_COUNT);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

一旦所有线程都忙,调用者的线程将执行任务。以下是对此类政策CallerRunsPolicy JavaDoc

的引用