【问题标题】:FixedThreadPool vs CachedThreadPool: the lesser of two evilsFixedThreadPool vs CachedThreadPool:两害相权取其轻
【发布时间】:2013-07-31 05:04:16
【问题描述】:

我有一个程序可以生成线程 (~5-150) 来执行一堆任务。最初,我使用FixedThreadPool,因为this similar question 建议它们更适合寿命更长的任务,并且由于我对多线程的了解非常有限,我认为线程的平均寿命(几分钟)“寿命长 em>”。

但是,我最近添加了生成额外线程的功能,这样做使我超出了我设置的线程限制。在这种情况下,最好是猜测并增加我可以允许的线程数还是切换到CachedThreadPool 这样我就不会浪费线程了?

初步试用它们,似乎没有区别,所以我倾向于使用CachedThreadPool,以避免浪费。但是,线程的寿命是否意味着我应该选择FixedThreadPool 并只处理未使用的线程? This question 让人觉得这些额外的线程并没有被浪费,但我希望能得到澄清。

【问题讨论】:

    标签: java multithreading threadpool executorservice java.util.concurrent


    【解决方案1】:

    CachedThreadPool 似乎适合您的情况,因为直接将其用于长时间运行的线程不会产生负面影响。 java 文档中关于 CachedThreadPools 适用于短任务的评论只是表明它们特别适用于这种情况,而不是它们不能用于长时间运行的任务。

    CachedThreadPool 的主要问题是它会创建多达Integer.MAX_VALUE 个线程,因为如果缓存中不存在未使用的线程,它总是会生成一个新线程。因此,如果您有长时间运行的任务,那么您更有可能增加并发线程的数量超过您的预期,因为这种类型的线程池不会限制有多少并发执行本身。如上所述,对于您的用例而言,这似乎不是问题,但需要注意。

    为了进一步详细说明 CachedThreadPoolFixedThreadPool 之间的区别,Executors.newCachedThreadPoolExecutors.newFixedThreadPool 都通过 @987654328 的实例由相同的线程池实现(至少在开放的 JDK 中)支持@,只是参数不同。区别只是它们的线程最小值、最大值、线程终止时间和队列类型。

    public static ExecutorService newFixedThreadPool(int nThreads) {
         return new ThreadPoolExecutor(nThreads, nThreads,
                                       0L, TimeUnit.MILLISECONDS,
                                       new LinkedBlockingQueue<Runnable>());
     }
    
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
    }
    

    FixedThreadPool 确实有其优势,当您实际上想要使用固定数量的线程时,您可以将任意数量的任务提交给执行器服务,同时知道线程数将保持在您指定的级别。如果您明确想要增加线程数,那么这不是合适的选择。

    然而,这确实意味着CachedThreadPool 可能遇到的一个问题是关于限制并发运行的线程数。 CachedThreadPool 不会为您限制它们,因此您可能需要编写自己的代码以确保不会运行太多线程,您可以通过使用所需的行为特征实例化自己的 ThreadPoolExecutor 来相对轻松地做到这一点。这实际上取决于您的应用程序的设计以及如何将任务提交给执行器服务。

    【讨论】:

    • “一个 CachedThreadPool 正是你应该在你的情况下使用的,因为对长时间运行的线程使用它没有负面影响”。我不认为我同意。 CachedThreadPool 动态创建没有上限的线程。在大量线程上长时间运行的任务可能会占用所有资源。此外,拥有比理想更多的线程可能会导致在这些线程的上下文切换上浪费太多资源。尽管您在答案末尾解释说需要自定义限制,但答案的开头有点误导。
    • 为什么不简单地创建一个有界的ThreadPoolExecutor,比如ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())
    【解决方案2】:

    FixedThreadPoolCachedThreadPool 在高负载应用程序中都是邪恶的。

    CachedThreadPoolFixedThreadPool 更危险

    如果您的应用程序负载高且需要低延迟,最好摆脱这两种选择,因为有以下缺点

    1. 任务队列的无限性质:它可能导致内存不足或高延迟
    2. 长时间运行的线程将导致CachedThreadPool 无法控制线程创建

    既然你知道两者都是邪恶的,那么较小的邪恶就没有任何好处。首选ThreadPoolExecutor,它提供对许多参数的精细控制。

    1. 将任务队列设置为有界队列以获得更好的控制
    2. 拥有正确的 RejectionHandler - 你自己的 RejectionHandler 或 JDK 提供的默认处理程序
    3. 如果您在任务完成之前/之后有事情要做,请覆盖 beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)
    4. 如果需要线程自定义,则覆盖 ThreadFactory
    5. 在运行时动态控制线程池大小(相关 SE 问题:Dynamic Thread Pool

    【讨论】:

    【解决方案3】:

    所以我有一个程序可以生成线程(~5-150)来执行一堆任务。

    您确定您了解您选择的操作系统和硬件实际上是如何处理线程的吗? Java 如何将线程映射到 OS 线程,如何将线程映射到 CPU 线程等?我问是因为在一个 JRE 中创建 150 个线程只有在下面有大量 CPU 内核/线程时才有意义,而这很可能不是这种情况。根据所使用的操作系统和 RAM,创建超过 n 个线程甚至可能导致 JRE 由于 OOM 错误而终止。所以你应该真正区分线程和这些线程要做的工作,你甚至能够处理多少工作等等。

    这就是 CachedThreadPool 的问题:在实际上无法运行的线程中排队长时间运行的工作是没有意义的,因为您只有 2 个 CPU 内核能够处理这些线程。如果最终有 150 个调度线程,则可能会为 Java 和操作系统中使用的调度程序同时处理它们创建大量不必要的开销。如果您只有 2 个 CPU 内核,这根本是不可能的,除非您的线程一直在等待 I/O 等。但即使在那种情况下,很多线程也会创建大量 I/O...

    FixedThreadPool 不会出现这个问题,它是用例如创建的。 2+n 个线程,其中 n 当然是合理的低,因为使用硬件和操作系统资源,管理无论如何都无法运行的线程的开销要少得多。

    【讨论】:

    • 有时没有更好的选择,你可能只有 1 个 CPU 核心,但如果你运行的服务器每个用户请求都会触发一个线程来处理请求,那么就没有任何其他合理的选择,特别是如果您计划在扩大用户群后扩展服务器。
    • @mFeinstein 如果可以选择线程池实现,怎么能没有选择呢?在您的示例中,只有 1 个 CPU 内核只产生更多线程根本没有任何意义,它非常适合我使用 FixedThreadPool 的示例。这也很容易扩展,首先使用一个或两个工作线程,然后根据内核数量使用 10 或 15 个。
    • 绝大多数的 Web 服务器实现都会为每个新的 HTTP 请求创建一个新线程......他们不会关心机器有多少实际内核,这使得实现更加简单和更容易扩展。这适用于许多其他设计,您只想编写一次代码并部署,如果您更改机器(可能是云实例),则不必重新编译和重新部署。
    • @mFeinstein 大多数 Web 服务器自己使用线程池来处理请求,仅仅是因为生成无法运行的线程没有意义,或者它们使用事件循环进行连接并在池中处理请求之后或之类的。此外,您错过了重点,即问题在于能够选择正确的线程池并生成无论如何都无法运行的线程仍然没有意义。根据内核配置为每台机器合理数量的线程的 FixedthreadPool 可以很好地扩展。
    • @ThorstenSchöning,在 2 核机器上拥有 50 个 CPU 绑定线程是没有帮助的。在 2 核机器上拥有 50 个 IO-bound 线程会很有帮助。
    猜你喜欢
    • 2015-12-23
    • 2013-07-09
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多