Java 8中使用CompletableFutures的Tomcat 8

3

我想并行化我的应用程序。我使用Tomcat8部署我的Web应用程序,并使用Tomcat默认设置(HTTP连接器线程数为200和默认JVM设置)。我想在Java中使用CompletableFuture并行完成任务。例如,如果我有3个任务TASK1、TASK2和TASK3,则不是按顺序执行它们,而是使用CompletableFuture在单独的线程中执行每个任务并合并结果。 我的问题是,任何时候Tomcat接收到200个请求,那么在Executor中创建多少个线程是安全的呢? 如果Executors.newFixedThreadPool(600),那么600是一个好的数字,因为在任何时候我都会得到200个请求和三个并行任务需要完成,所以我需要至少600个线程(理论上)。我认为创建更多线程可能会降低性能。

2个回答

3

你能够创建多少个线程取决于许多因素,主要在于机器和操作系统的规格。

这篇答案说道。

这取决于您使用的CPU、操作系统、其他进程正在做什么、您使用的Java版本以及其他因素。我曾看到过一台Windows服务器在崩溃之前拥有>6500个线程。

我个人曾使用过近1000个线程,我的机器性能仍然良好。

关于使用 Executors.newFixedThreadPool(600),您必须分析它是否是最适合您的应用程序特征和需求的执行器类型。

在这里,您可以看到 FixedThreadPoolCachedThreadPool 之间的比较:

FixedThreadPool vs CachedThreadPool: the lesser of two evils

如果常量线程池(600个)大部分时间都处于空闲状态,您可以使用缓存线程池,它将创建尽可能多的线程,然后将它们保留一段时间或在继续使用时保持它们。如果您有200个不断执行3个任务,则可能会受益于使用固定线程池。
您还可以使用具有最大线程数的自定义线程工厂的CachedThreadPool。
另一方面,如果大多数任务都是短期任务,则可以使用Executors.newWorkStealingPool(),这确保您的可用CPU核心始终在工作,并使用Runtime.getRuntime().availableProcessors()设置并行级别,如果某个线程完成其工作,则可以从另一个线程队列中窃取任务。
您可以了解更多关于ForkJoinPool和Executors.newWorkStealingPool()的信息(注意:newWorkStealingPool在内部使用ForkJoinPool)。

Java8中ForkJoinPool和Executors.newWorkStealingPool的详细区别?


感谢@Jose Da Silva。在确定实际线程数之前,我将进行一些试错方法。 - Nandeesh
不客气,我认为这是你能做到的最好的,如果你找到适合你的应用程序的线程数,并想要使用缓存的线程池,你也可以通过自定义线程工厂指定最大线程数。 - Jose Da Silva

2
《答案》是Jose Da Silva的正确而聪明的回答。这里再解释一下。
没有硬性规则可遵循。正如其他人所说,它取决于许多因素,例如具体任务的性质、它们的持续时间、任务对CPU的密集程度以及任务在等待资源时的频率、线程调度程序在您的主机操作系统和JVM中的工作方式、CPU的性质、CPU中的核心数量等等。
请记住,线程的调度不是免费的。为了执行线程,调度线程需要一定的开销。在切换线程时也有开销,即上下文切换。超线程是一种硬件特性,用于减少上下文切换的成本,但即使如此,在一个核心上切换两个以上的线程仍然需要进行完整的上下文切换。
因此,认为线程越多越好是天真的想法。往往情况是,线程过多会导致上下文切换过于频繁,反而降低了性能。一般来说,除非这些线程大部分时间都处于空闲状态,否则数百个活跃线程可能会适得其反。同时请记住,您的应用程序(Tomcat + Web 应用程序)不是主机上活跃线程的唯一来源。操作系统和其他应用程序可能正在运行几十个略微活跃的线程和一些更繁忙的线程(本地 I/O、网络等)。
例如,如果您有一个启用超线程技术的4核CPU,那就意味着有8个逻辑核心,因此您可以期望在专用Tomcat机器上使用5个左右的核心。如果您的线程大约三分之一的时间都很忙(CPU 密集型),那么您可能需要从12-20个线程池开始。如果线程只有不到5-10% 的时间很忙,那么也许需要100个线程池。然后监视实际性能并观察情况如何。如果所有核心在连续几分钟内都以100% 的利用率高速运转,则可能过度订阅,需要减小线程池的大小。
关于持续时间,如果线程的生命周期短暂,但在服务器使用高峰期可能会有大量线程同时运行,您可能需要将线程池大小保持较小,以避免太多的线程同时争夺CPU资源。如果您有许多线程,并且每个线程都非常忙碌,例如加密或编解码等,那么您需要将线程池大小限制在物理核心数以下。对于上面的示例,将线程池限制为四个物理核心(8个逻辑超线程核心)中的两个或三个,留下物理核心用于操作系统或其他应用程序的进程。实际上,如果您确实有这样的非常耗费CPU的任务,您可能考虑禁用部署计算机上的超线程。逻辑核心对每个物理核心进行超线程配对,它们不会同时执行,它们相互切换,上下文切换成本较低,但不是零成本。如果您的任务极度耗费CPU(在普通商业应用程序中相当罕见),没有等待资源休息的时间,则超线程可能没有任何好处。当然,您无法准确地知道特定Web应用程序和部署的需求,只能通过试错来确定最佳设置。
提示:与其硬编码线程池大小,您可能希望将线程池的大小外部化,以允许您在部署时进行更改。可以通过JNDI或其他外部来源设置要检索的值。

谢谢@Basil Bourque。在确定实际线程数量之前,我会进行试错方法。 - Nandeesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接