Tomcat 8 与 Java 8 中的 CompletableFutures

Tomcat 8 with CompletableFutures in Java 8

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

可以创建多少个线程取决于很多因素,主要是机器的规格和OS。

这个answer说。

This depends on the CPU you're using, on the OS, on what other processes are doing, on what Java release you're using, and other factors. I've seen a Windows server have > 6500 Threads before bringing the machine down.

我个人用了将近1000个线程,我机器的性能还是不错的。

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

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

FixedThreadPool vs CachedThreadPool: the lesser of two evils

如果常量线程池(600 个),大多数线程大部分时间都处于空闲状态,您可以使用 chache 线程池,它会根据需要创建尽可能多的线程,然后将它们保留一定时间或者只要它们继续被使用。如果您让 200 不断执行这 3 个任务,您可能会从使用固定线程池中获益。

您还可以使用 CachedThreadPool 和使用自定义线程工厂创建的最大线程数。

另一方面,如果大多数任务都是短任务,您可以使用 Executors.newWorkStealingPool() 这确保您可用的 cpu 内核始终工作,设置并行级别 Runtime.getRuntime().availableProcessors(),如果某个线程完成了它的工作,它可以从另一个线程队列中窃取任务。

您可以查看更多关于 ForkJoinPoolExecutors.newWorkStealingPool() 的信息(注意:newWorkStealingPool 在内部使用 ForkJoinPool):

Jose Da Silva 的

是正确和聪明的。这里有更多的解释。

没有hard-and-fast规则可遵循。正如其他人所说,这取决于许多因素,例如特定任务的性质、它们的持续时间、任务如何 CPU-intensive 与任务等待资源的频率、线程调度程序在主机中的工作方式 OS 和您的 JVM,您的 CPU 的性质,您的 CPU 中有多少个核心,等等。

请记住,scheduling of threads is not free-of-cost. There is overhead in scheduling the the threads for their moments of execution. There is expense in changing between threads, the context switch. Hyper-threading 是一项硬件功能,可以减少上下文切换的成本,但即便如此,在一个内核的两个以上线程之间切换也会恢复到完整 context-switch .

所以,认为线程“越多越好”的想法是天真的。很多时候,您可能会发现 更少的 线程比具有太多 context-switch 线程的 too-many 线程性能更高。

一般来说,可能有数百个活动线程 counter-productive,除非这些线程大部分时间无所事事。请记住,您的应用程序(Tomcat + 网络应用程序)并不是主机上活动线程的唯一来源。 OS 和其他应用程序可能 运行 数十个 somewhat-active 线程,以及一些更繁忙的线程(本地 I/O、网络等)。

例如,如果您有一个启用了 hyper-threading 的 4 核心 CPU,这意味着 8 个逻辑核心,因此您可能希望使用 5 个左右的核心 Tomcat 机器。如果您的线程有大约三分之一的时间处于忙碌状态(CPU 密集型),那么您可能希望从大约 12-20 个线程池开始。如果线程仅在不到 5-10% 的时间内忙碌,则可能是 100 池。然后监视 real-world 性能并查看其运行情况。如果所有内核一次都以 100% 的利用率爆发几分钟,那么您可能 over-subscribing 并且可能想要减小 thread-pool 的大小。

关于持续时间,如果线程是 short-lived,但在您的服务器使用高峰期可能有很多线程,您可能需要保持池较小以避免太多线程吵着要CPU 同时。

如果您有很多线程,并且每个线程都非常忙于 CPU,例如加密或编解码器,那么您希望线程池大小限制为小于物理 核心的数量。对于我们上面的示例,将池限制为四个物理内核中的两个或三个(8 个逻辑 hyper-threaded 内核),让物理内核为 OS 或其他应用程序的进程打开。事实上,如果您真的有非常 CPU-intensive 的任务,您可能会考虑在您的部署计算机上禁用 hyper-threading。 Hyper-threaded 每个物理核心的逻辑核心对 不是 同时执行,它们 trade-off back-and-forth 上下文切换的成本较低但不是 成本。如果您的任务非常CPU-intensive(在常见的商业应用程序中很少见),没有 waiting-on-resource 休息时间,那么 hyper-threading 可能没有任何好处。

当然,您无法确切知道上面讨论的特定部署的特定 Web 应用程序所要求的数字。 Trial-and-error 是唯一的出路。

提示:您可能希望外部化线程池的大小,而不是 hard-code 您的 thread-pool 大小,以允许您在 deployment-time 处进行更改。也许设置要通过 JNDI 或其他一些外部源检索的值。