一文说清楚,线程池大小该如何设置
  GMmxlVN9lf1V 2023年11月14日 18 0

Java线程的创建会花费不少时间,还得让JVM和操作系统忙活,可累人了。所以,为了减少这些额外的开销,就出现了线程池的技术,这可真是帮了大忙。

那么,我们接下来要探讨一下,怎么确定理想的线程池大小,才能让系统表现最好,还能轻松应对突然增加的工作量。不过,咱们也得记住,就是用了线程池,线程管理本身也可能成为瓶颈哦。

线程池的好处

  • 性能:创建和销毁线程很费劲,特别是在Java中。线程池可以创建一些可以重复用的线程,用来帮助减少这种开销。
  • 能扩展:线程池可以根据应用程序的需求来扩展。比如,要是负载很重的话,就可以把线程池扩展一下,处理更多的任务。
  • 能合理管理资源:线程池可以帮助管理使用的资源。比方说,线程池可以限制在任何给定时间能活动的线程数量,这样就能防止应用程序用光内存。

调整线程池大小:了解系统和资源的限制

在调整线程池大小时,了解系统的限制(包括硬件和外部依赖性)特别重要。咱们用一个例子来详细说明一下这个概念:

 一文说清楚,线程池大小该如何设置_应用程序

场景:

假设你正在开发一个处理HTTP请求的Web应用程序。每个请求可能涉及到处理数据库中的数据以及调用外部第三方服务。你的目标是确定能有效地处理这些请求的最佳线程池大小。

需要考虑的因素:

假设你使用HikariCP等连接池来管理数据库连接,并且已将其配置为最多允许100个连接。如果你创建的线程比这个连接数还多,那些额外的线程就得等着有连接可用,这样就会导致资源争用,可能会引发性能问题。

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

public class DatabaseConnectionExample {
    public static void main(String[] args) {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        config.setUsername("root");
        config.setPassword("root");
        config.setMaximumPoolSize(100); // 设置最大连接数

        HikariDataSource dataSource = new HikariDataSource(config);

        // 使用dataSource获取数据库连接并执行查询。
    }
}

外部服务吞吐量:除了处理外部服务请求的能力之外,还要考虑服务之间的交互和数据传输速度。

比如,如果两个服务之间需要频繁地传输大量数据,那么就需要看看网络带宽和数据传输速度能不能满足要求,要不然数据传输速度慢可能会导致服务响应时间变长,进而影响整个程序的性能。

CPU 核心:要优化线程池大小的时候,知道服务器上有多少 CPU 核心真的很重要。

比如,如果服务器有4个CPU核心,那么线程池大小设置为4或5可能是一个合适的选择,这样可以充分利用服务器的资源,同时避免过多的线程去争抢CPU资源。但是,如果服务器只有一个CPU核心,那么线程池的大小就不能设置得太大,否则可能会导致线程之间的竞争过于激烈,反而降低应用程序的性能。所以,设置线程池大小的时候,要根据服务器的硬件配置和应用程序的实际情况综合考虑。

int  numOfCores  = Runtime.getRuntime().availableProcessors();

每个CPU核心可以同时执行一个线程。如果超过线程的 CPU 核心数量可能会导致频繁的上下文切换,从而大大降低性能。


介绍CPU密集型和IO密集型任务

CPU密集型任务和IO密集型任务是两种不同类型的计算任务,它们的性质和资源需求有所不同。

  1. CPU密集型任务:
  • CPU密集型任务是指需要大量计算能力的任务,例如数学运算、图形处理、数据分析、编码和解码视频等。
  • 这些任务通常会占用大量的CPU资源,因为它们需要处理大量的计算操作,而与硬盘或网络通信的频率相对较低。
  • 通常情况下,提高CPU性能(如提高CPU时钟频率或使用多核CPU)可以改善CPU密集型任务的执行性能。

多线程和并行性:并行处理是一种技术,用于将较大的任务划分为较小的子任务,并将这些子任务分布在多个 CPU 核心或处理器上,以利用并发执行并提高整体性能。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ParallelSquareCalculator {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        // 获取cpu核心数
        int numThreads = Runtime.getRuntime().availableProcessors(); 
        ExecutorService executorService = Executors.newFixedThreadPool(numThreads);

        for (int number : numbers) {
            executorService.submit(() -> {
                int square = calculateSquare(number);
                System.out.println("Square of " + number + " is " + square);
            });
        }

        executorService.shutdown();
        try {
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static int calculateSquare(int number) {
        // 模拟一次耗时的计算
        try {
            Thread.sleep(1000); // Simulate a 1-second delay
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return number * number;
    }
}


  1. IO密集型任务:
  • IO密集型任务是指需要大量的输入/输出操作(例如读取文件、数据库查询、网络通信)的任务。
  • 这些任务通常不需要大量的计算能力,但需要等待IO操作完成,因此它们可能会占用大量的时间。
  • 对于IO密集型任务,提高CPU性能通常不会显著提高性能,因为瓶颈通常是在IO操作上。
  • 优化IO密集型任务的关键在于减少等待时间,例如使用异步IO、多线程或多进程等技术。

优化逻辑:

  • 缓存:将频繁访问的数据缓存在内存中,以减少重复 I/O 操作的需要。
  • 负载平衡:将 I/O 密集型任务分配到多个线程或进程,以有效处理并发 I/O 操作。
  • SSD 的使用:与传统硬盘驱动器 (HDD) 相比,固态驱动器 (SSD) 可以显着加快 I/O 操作速度。
  • 使用高效的数据结构(例如哈希表和 B 树)来减少所需的 I/O 操作数量。
  • 避免不必要的文件操作,例如多次打开和关闭文件。

要理解一个任务是CPU密集型还是IO密集型,可以考虑任务的特点:

  • 如果任务的主要瓶颈是计算操作,它很可能是CPU密集型。
  • 如果任务的主要瓶颈是等待外部资源(例如磁盘、网络)的可用性,它很可能是IO密集型。

在实际应用中,优化CPU密集型任务通常涉及提高CPU性能,而优化IO密集型任务通常涉及减少IO操作的延迟,以提高整体性能。

确定线程数

如果是CPU 密集型任务较多的线程数:

  1. 计算可用 CPU 核心数:在 Java 中用于Runtime.getRuntime().availableProcessors()确定可用 CPU 核心的数量。假设您有 8 个核心。
  2. 创建线程池:创建大小接近或略小于可用CPU核心数的线程池。在这种情况下,您可以选择 6 或 7 个线程,为其他任务和系统进程留下一些 CPU 容量。

如果是IO密集型

对于 I/O 密集型任务,最佳线程数通常由 I/O 操作的性质和预期延迟决定,不一定等于CPU核心的数量。

确定 I/O 密集型任务的线程数:

  1. 分析 I/O 延迟:估计预期的 I/O 延迟,这取决于网络或存储。例如,如果每个 HTTP 请求大约需要 500 毫秒才能完成,您可能需要适应 I/O 操作中的一些重叠。
  2. 创建线程池:创建一个大小能够平衡并行性与预期 I/O 延迟的线程池。每个任务不一定需要一个线程;相反,您可以使用较小的池来有效管理 I/O 密集型任务。

计算线程池线程数公式

确定线程池大小的公式可以写成如下:

线程数 = 可用核心数 * 目标 CPU 利用率 * (1 + 等待时间 / 服务时间)

可用核心数:这是您的应用程序可用的CPU 核心数。需要注意的是,这与 CPU 的数量不同,因为每个 CPU 可能有多个核心。

目标 CPU 利用率:这是您希望应用程序使用的CPU 时间的百分比。如果您将目标 CPU 利用率设置得太高,您的应用程序可能会变得无响应。如果设置得太低,您的应用程序将无法充分利用可用的 CPU 资源

等待时间:这是线程等待 I/O 操作完成所花费的时间。这可能包括等待网络响应、数据库查询或文件操作。

服务时间:这是线程执行计算所花费的时间量。

阻塞系数:这是等待时间与服务时间的比率。它衡量线程等待 I/O 操作完成所花费的时间相对于执行计算所花费的时间。

用法示例

假设您有一台具有 4 个 CPU 核心的服务器,并且您希望应用程序使用 50% 的可用 CPU 资源。

您的应用程序有两类任务:I/O 密集型任务和 CPU 密集型任务。

I/O 密集型任务的阻塞系数为 0.5,这意味着它们花费 50% 的时间等待 I/O 操作完成。

线程数 = 4 核 * 0.5 * (1 + 0.5) = 3 线程

CPU 密集型任务的阻塞系数为 0.1,这意味着它们花费 10% 的时间等待 I/O 操作完成。

线程数 = 4 核 * 0.5 * (1 + 0.1) = 2.2 线程

在此示例中,您将创建两个线程池,一个用于 I/O 密集型任务,另一个用于 CPU 密集型任务。I/O 密集型线程池将有 3 个线程,CPU 密集型线程池将有 2 个线程。


如果各位觉得老七的文章还不错的话,麻烦大家动动小手,

点赞、关注、转发走一波!!

有任何问题可以评论区留言或者私信我,我必将知无不言言无不尽!

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月14日 0

暂无评论

推荐阅读
GMmxlVN9lf1V