线程池大小设置多少,比较合适?

CPU密集型任务是那些需要大量处理能力的任务,例如执行复杂的计算或运行模拟。这些任务通常受到 CPU 速度的限制,而不是 I/O 设备的速度。

  • 对音频或视频文件进行编码或解码
  • 编译和链接软件
  • 运行复杂的模拟
  • 执行机器学习或数据挖掘任务
  • 玩电子游戏

优化:

多线程和并行性: 并行处理是一种将较大的任务划分为较小的子任务并将这些子任务分布在多个 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};
        int numThreads = Runtime.getRuntime().availableProcessors(); // Get the number of CPU cores
        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) {
        // Simulate a time-consuming calculation (e.g., database query, complex computation)
        try {
            Thread.sleep(1000); // Simulate a 1-second delay
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return number * number;
    }
}

IO密集型任务是那些与存储设备交互的设备(例如,读/写文件)、网络套接字(例如,进行 API 调用),或用户输入(例如,图形用户界面中的用户交互)。

  • 将大文件读取或写入磁盘(例如,保存视频文件、加载数据库)
  • 通过网络下载或上传文件(例如,浏览网页、观看流媒体视频)
  • 发送和接收电子邮件
  • 运行网络服务器或其他网络服务
  • 执行数据库查询
  • 处理传入请求的 Web 服务器。

优化:

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

确定线程数

4对于 CPU 密集型任务:

对于 CPU 密集型任务,我们希望最大限度地提高 CPU 利用率,但又不会因为过多的线程而压垮系统,否则会导致过多的上下文切换。一个常见的经验法则是使用可用的 CPU 核心数量

现实生活中的例子:视频编码

想象一下我们有一个可用的多核 CPU,此时正在开发一个视频处理应用程序。视频编码是一项 CPU 密集型任务,我们需要应用复杂的算法来压缩视频文件。搜索Java知音公众号,回复“Java题库”,送你一份Java面试宝典

确定 CPU 密集型任务的线程数:

计算可用 CPU 核心数:在 Java 中用于Runtime.getRuntime().availableProcessors()确定可用 CPU 核心的数量。假设我们有 8 个核心。

创建线程池: 创建大小接近或略小于可用CPU核心数的线程池。在这种情况下,我们可以选择 6 或 7 个线程,为其他任务和系统进程留下一些 CPU 容量。

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

public class VideoEncodingApp {
    public static void main(String[] args) {
        int availableCores = Runtime.getRuntime().availableProcessors();
        int numberOfThreads = Math.max(availableCores - 1, 1); // Adjust as needed

        ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads);

        // Submit video encoding tasks to the thread pool.
        for (int i = 0; i < 10; i++) {
            threadPool.execute(() -> {
                encodeVideo(); // Simulated video encoding task
            });
        }

        threadPool.shutdown();
    }

    private static void encodeVideo() {
        // Simulate video encoding (CPU-bound) task.
        // Complex calculations and compression algorithms here.
    }
}

5对于 I/O 密集型任务:

对于 I/O 密集型任务,最佳线程数通常由 I/O 操作的性质和预期延迟决定。我们希望有足够的线程来保持 I/O 设备繁忙而不会使它们过载。理想的数量不一定等于CPU核心的数量。

现实生活中的例子:网页抓取

考虑构建一个网页爬虫来下载网页并提取信息。这涉及到发出HTTP 请求,由于网络延迟,这些请求是 I/O 密集型任务。

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

分析 I/O 延迟: 估计预期的 I/O 延迟,这取决于网络或存储。例如,如果每个 HTTP 请求大约需要 500 毫秒才能完成,我们可能需要适应 I/O 操作中的一些重叠。

创建线程池: 创建一个大小能够平衡并行性与预期 I/O 延迟的线程池。每个任务不一定需要一个线程;相反,我们可以使用较小的池来有效管理 I/O 密集型任务。

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

public class WebPageCrawler {
    public static void main(String[] args) {
        int expectedIOLatency = 500; // Estimated I/O latency in milliseconds
        int numberOfThreads = 4; // Adjust based on your expected latency and system capabilities

        ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads);

        // List of URLs to crawl.
        String[] urlsToCrawl = {
            "https://example.com",
            "https://google.com",
            "https://github.com",
            // Add more URLs here
        };

        for (String url : urlsToCrawl) {
            threadPool.execute(() -> {
                crawlWebPage(url, expectedIOLatency);
            });
        }

        threadPool.shutdown();
    }

    private static void crawlWebPage(String url, int expectedIOLatency) {
        // Simulate web page crawling (I/O-bound) task.
        // Perform HTTP request and process the page content.
        try {
            Thread.sleep(expectedIOLatency); // Simulating I/O latency
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

6我们可以遵循具体的公式吗?

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

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

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

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

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

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

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

7用法示例

假设我们有一台具有 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 个线程。

这是根据大量的案例总结的Java线程池大小确定的公式,但在实操中所考虑的侧重点可能有不同,那么需要根据实际场景来微调,本文提供一种确定最优的思路,希望对你开发中确定线程池大小有所帮助!

扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论