的ThreadPoolExecutor#执行。如何重用正在运行的线程?

时间:2018-04-02 17:45:01

标签: java multithreading

我曾经多次使用ThreadPoolExecutors,这也是主要原因之一 - 它旨在“更快”地处理许多请求,因为并行性和“随时可用”的线程(尽管还有其他)。

现在我一直坚持以前所熟知的内心设计 这是来自java 8 ThreadPoolExecutor的片段:

public void execute(Runnable command) {
    ...
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     */
    ...
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
...

我对第一步感兴趣,因为在大多数情况下,您不希望线程轮询执行程序在内部队列中存储“未处理的请求”,最好将它们保留在外部输入Kafka主题/ JMS队列等中。因此,我通常设计我的性能/并行性执行器,以实现零内部容量和“调用者运行拒绝策略”。你选择了一些理智的大量并行线程和核心池超时不会吓到其他人,并显示该值有多大;)。我不使用内部队列,我希望任务越早开始处理越好,因此它已成为“固定线程池执行器”。因此,在大多数情况下,我处于方法逻辑的“第一步”。

这是一个问题:这是不是'重复'现有线程,但每次“核心大小”(大多数情况下)都会创建新线程? “只有在所有其他人都忙的时候才能添加新的核心线程”而不是“当我们有机会在另一个线程创建上吮吸一段时间”时,这不是更好吗?我错过了什么吗?

3 个答案:

答案 0 :(得分:1)

doc描述了corePoolSize,maxPoolSize和任务队列之间的关系,以及提交任务时会发生什么。

...但每次在核心尺寸下创建新的[线程] ......&#39;

是。来自doc:

  

在方法execute(Runnable)中提交新任务时,更少   比corePoolSize线程正在运行,创建一个新线程   处理请求,即使其他工作线程处于空闲状态。

只有在所有其他人忙碌的情况下才添加新的核心线程会不会更好......

由于您不想使用内部队列,这似乎是合理的。因此,将corePoolSize和maxPoolSize设置为相同。一旦完成创建线程的提升,就不会再创造了。

但是,如果外部队列的增长速度快于可处理的速度,那么使用CallerRunsPolicy似乎会损害性能。

答案 1 :(得分:0)

  

这是一个问题:这是否真的不会重复使用&#39;现有的线程,但每次在核心大小的情况下都会创建一个新的线程&#39; (大多数情况下)?

是的,这就是代码的记录和编写方式。

  

我错过了什么吗?

是的,我认为你错过了&#34;核心&#34;线程。核心线程在Executors docs中定义为:

... threads to keep in the pool, even if they are idle.

这就是定义。线程启动是一个非常简单的过程,因此如果池中有10个核心线程,则前10个池中的每个请求都会启动一个线程,直到所有核心线程都处于活动状态。这会将启动负载分散到前X个请求中。这不是关于完成任务,这是关于初始化TPE和分散线程创建负载。如果您不想要这种行为,可以致电prestartAllCoreThreads()

核心线程的全部目的是让线程已经启动并运行,可以立即处理任务。如果我们每次需要时都必须启动一个线程,那么就会有不必要的资源分配时间和线程启动/停止开销,从而占用计算和OS资源。如果您不想要核心线程,那么您可以让它们超时并支付启动时间。

  

我以前使用ThreadPoolExecutors多年来是其中一个主要原因 - 它的设计目标是“更快”。处理许多请求是因为并行性和随时可用的&#39;线程(还有其他)。

TPE不一定&#34;更快&#34;。我们使用它是因为手动管理和与多个线程进行通信很难并且容易出错。这就是TPE代码如此强大的原因。操作系统线程为我们提供了并行性。

  

我没有使用内部队列,我希望任务开始越早越好,

线程程序的重点是最大化吞吐量。如果在4核系统上运行100个线程并且任务是CPU密集型的,那么您将为增加的上下文切换付费,并且处理大量请求的总时间将减少。您的应用程序也很可能与其他程序竞争服务器上的资源,如果100个作业同时尝试在线程池中运行,您不希望它导致爬行速度变慢。

限制你的核心线程的全部意义(即使它们成为#34;理智的大量&#34;)是有最佳数量的并发线程将最大化整体应用程序的吞吐量。找到最佳的核心螺纹尺寸可能非常困难,但如果可能的话,实验会有所帮助。

它在很大程度上取决于任务中CPU与IO的程度。如果任务正在对慢速服务进行远程RPC调用,那么在池中拥有大量核心线程可能是有意义的。但是,如果它们主要是CPU任务,那么您将希望更接近CPU /核心数,然后排队其余任务。同样,这完全取决于整体吞吐量。

答案 2 :(得分:-1)

要重用线程,需要以某种方式将任务转移到现有线程 这促使我走向同步队列和零核心池大小。

return new ThreadPoolExecutor(0, maxThreadsCount,
        10L, SECONDS,
        new SynchronousQueue<Runnable>(),
        new BasicThreadFactory.Builder().namingPattern("processor-%d").build());

我的'主要流量'确实减少了500 - 1500(ms)的“峰值” 但这只适用于零大小的队列。对于非零大小的队列问题仍然是开放的。