12

我们有一个场景,提交给 ThreadPoolExecutor 的任务长时间运行。当线程池启动时,我们以核心池大小 = 5、最大池大小 = 20 和队列大小 10 启动它。在我们的应用程序中,大约有 10 个任务被提交。大多数情况下,这些任务运行几分钟/小时,然后完成。然而,有一种情况是所有 5 个任务都挂在 I/O 上。结果,我的核心池大小达到了最大值,但我的 Threadpoolexecutor 队列未满。所以额外的 5 个任务永远没有机会运行。请建议我们如何处理这种情况?在这种情况下有一个较小的队列更好的选择吗?初始化线程池时,最佳队列大小是多少?

另外关于挂起的任务,有什么办法可以将线程从线程池中拉出来?在这种情况下,至少其他任务将有机会运行。

4

6 回答 6

13

总体情况是这样的:

core pool size = 5,
max pool size = 20 and 
queue size of 10

提交了 10 个任务。其中有

  1. 5 个任务挂在 I/O => 核心池大小的所有线程都被占用。因此没有空闲线程。
  2. 剩下5个任务。这 5 个线程被排入队列,因为没有空闲线程并且队列可以容纳 10 个任务。在队列已满或核心池中的任何线程空闲之前,这些排队的任务将不会执行。

因此,您的程序被挂起。

在这里了解更多关于ThreadPoolExecutor手表动态的信息。该文档的要点如下:

  • 如果运行的线程少于 corePoolSize,则 Executor 总是更喜欢添加新线程而不是排队。
  • 如果 corePoolSize 或更多线程正在运行,Executor 总是更喜欢排队请求而不是添加新线程。
  • 如果请求无法排队,则会创建一个新线程,除非这将超过 maximumPoolSize,在这种情况下,该任务将被拒绝。

编辑
如果您希望增加核心池大小,则可以使用setCorePoolSize(int corePoolSize)。如果您增加corepoolsize新的线程,则将在需要时启动以执行任何排队的任务。

于 2013-02-22T20:18:07.950 回答
9

ThreadPoolExecutor的Javadocs指出:

任何 BlockingQueue 都可以用来传输和保存提交的任务。此队列的使用与池大小交互:

  • 如果运行的线程少于 corePoolSize,则 Executor 总是更喜欢添加新线程而不是排队。
  • 如果 corePoolSize 或更多线程正在运行,Executor 总是更喜欢排队请求而不是添加新线程。
  • 如果请求无法排队,则会创建一个新线程,除非这将超过 maximumPoolSize,在这种情况下,该任务将被拒绝。

除非在 5 个线程“挂起”后超过队列大小,否则您不会获得更多线程。

真正的答案是:解决导致线程挂起的问题。否则,如果线程运行时间过长,您将不得不实现一些使用Future返回的 s来取消线程的方案。submit()

于 2013-02-22T20:19:06.260 回答
2

我认为另一种方法是根据队列中等待的任务数设置 corePoolSize 。可以使用 setCorePoolSize 控制 corePoolSize。一个示例监视器线程可以控制您的 threadPoolExecutor。您还可以改进此监视器以调整并行度。

    public class ExecutorMonitor extends Thread{

            ThreadPoolExecutor executor = null;
            int initialCorePoolSize;
            public ExecutorMonitor(ThreadPoolExecutor executor)
            {
                this.executor = executor;
                this.initialCorePoolSize = executor.getCorePoolSize();
            }
            @Override
            public void run()
            {
                while (true)
                {   
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (executor.getQueue().size() > 0)
                    {
                        if(executor.getActiveCount() < executor.getMaximumPoolSize())
                            executor.setCorePoolSize(executor.getCorePoolSize() + 1);
                    }
                    if (executor.getQueue().size() == 0)
                    {
                        if(executor.getCorePoolSize() > initialCorePoolSize)
                            executor.setCorePoolSize(executor.getCorePoolSize() -1);
                    }
                }
            }
        }
于 2013-10-05T04:57:42.400 回答
0

jdk 中“可变”大小的线程池功能有点棘手。基本上,由于您概述的原因,您使用的设置不会很好地工作。当我想要一个可变大小的线程池时,我通常使用这个设置:

   ThreadPoolExecutor executor = new ThreadPoolExecutor(maxPoolSize, maxPoolSize,
                                                         DEFAULT_THREAD_TIMEOUT, DEFAULT_THREAD_TIMEOUT_UNIT,
                                                         new LinkedBlockingQueue<Runnable>(),
                                                         threadFactory);
    executor.allowCoreThreadTimeOut(true);

这将创建一个可变大小的线程池,该线程池将在 1 和 maxPoolSize 线程之间变化。由于核心大小与最大大小相同,因此池将始终更喜欢添加线程而不是排队,因此在线程数达到最大值之前您永远不会备份队列。

更新:至于您的挂起任务问题,如果任务的长度有上限,您可以让单独的经理跟踪未完成的 Future 并在它们超过最大运行时间时取消它们。当然,这假设您的任务是可中断的。

于 2013-02-22T20:24:37.587 回答
0

根本问题是用户确实获得了具有无限队列场景的 CORE_POOL_SIZE 线程。因此,如果核心池中有 5 个线程是他可以使用的全部,那么 max size 无济于事。虽然在所有情况下都建议减少线程执行时间,但在生产场景中,我们通常无法控制第三方服务的行为方式,因此解决方案是将核心池大小增加到等于最大池大小或限制队列大小.

于 2013-05-13T14:16:21.347 回答
-4

我想最好的解决方案是覆盖 ThreadPoolExecutor 的执行方法并引入其中的更改。

于 2013-10-10T11:19:17.983 回答