线程池参数理解

参考理论

工作中对于线程池核心数与最大线程数的配置,需要根据业务实际需求来进行配置。对于业务功能任务,通常可分为三种:CPU密集型、IO密集型、混合型。

CPU密集型

CPU密集型任务,也被称作计算密集型任务,指的是那些在执行过程中,主要依赖于中央处理器(CPU)的计算能力来完成任务的工作负载。这类任务的特点是需要进行大量的数据处理、计算和逻辑判断,如数值计算、图像处理、视频编码等。在执行这些任务时,CPU的利用率通常很高,而其他资源如磁盘、网络等可能处于较空闲的状态。

java 复制代码
    static class DefaultRunnable implements Runnable {
        @Override
        public void run() {
            int i = 0;
            while (i < 100000) {
                i++;
            }
        }
    }

当机器只有一个CPU,而我们需要运算两次 while循环去计算结果,运算需要时间记为t1,t2。 如果拆分出来用两个任务去分别运算,总时间就是:t1+t2+线程切换时间;如果在一个任务里运算,总时间就是:t1+t2。

由此,在CPU密集型任务配置线程数时最好是与CPU核心数一致,有多少个核心数就设置多少个线程数:

java 复制代码
// 获取CPU核心数
Runtime.getRuntime().availableProcessors();

只不过,为了应对线程执行过程发生缺页中断或其他异常导致线程阻塞的请求,我们可以额外在多设置一个线程,这样当某个线程暂时不需要CPU时,可以有替补线程来继续利用CPU。

所以,对于CPU密集型任务,我们可以将线程数设置为:CPU核心数+1

IO密集型

IO密集型任务指的是在执行过程中主要涉及到输入输出(IO)操作的任务。这些任务通常需要与外部资源进行交互,如读写文件、网络请求、数据库查询等。在这类任务中,CPU处理能力并不是主要的瓶颈,而是磁盘I/O或网络I/O操作所花费的时间占据了主导地位。

java 复制代码
    static class DefaultRunnable implements Runnable {
        @Override
        public void run() {
            try (OutputStreamWriter writer = new OutputStreamWriter(
                    Files.newOutputStream(new File("1.log").toPath()))) {
                writer.write(1111);
                writer.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

由于IO耗时严重,而即使是单核的CPU,当某一个线程因为IO耗时太久时,CPU可以执行其余的线程,这并不会过多影响到CPU的使用。

通常,如果IO型任务执行时间越长,那么阻塞在IO上的线程就会越多,相对的就可以设置更多的线程让CPU调度。 但是也并不是绝对的越多越好。

对于IO密集型任务,可以通过此公式来计算:线程数 = CPU核心数 * (1 + 线程等待时间 / 线程运行总时间)

需要注意的是,以上理论不去考虑系统调用,而在真实项目环境中,还有存在很多的因素影响。因此,以上的理论仅是一个参考,还需要结合 项目压测来获取一个合适的值。

参数配置

创建一个线程池通常是通过ThreadPoolExecutor类的构造函数来实现的,该构造函数需要传递多个参数来配置线程池的行为,分别是:

  • 核心线程数 线程池维护线程的最少数量。在没有任务执行时,线程池会保留这些核心线程。当任务到达时,核心线程会立即执行任务。如果核心线程数设为0,则任务到达时会根据工作队列和最大线程数的设置来创建新线程。
  • 最大线程数 线程池能够容纳的最大线程数目。当工作队列满了并且没有空闲线程执行任务时,如果此时线程池中的线程数小于最大线程数,线程池会创建新线程直到达到最大线程数。
  • 非核心线程存活时间 & 存活时间单位 这两个参数一起用来设置非核心线程的空闲存活时间。如果线程数大于核心线程数,那么空闲时间超过keepAliveTime的线程将被终止。unit指定了keepAliveTime的时间单位。
  • 工作队列 用于存放待执行的任务。它是一个阻塞队列,用于在任务提交阶段存储任务并在线程池中的线程可用时提供任务。线程池会根据工作队列的类型和配置来决定任务的执行策略。
  • 线程工厂 用于创建新线程。可以通过线程工厂来设置新线程的名称、线程组等属性。
  • 拒绝策略 当线程池已经达到最大容量并且工作队列也满了时,线程池会使用拒绝策略来处理无法执行的任务。
java 复制代码
      Executor executor = new ThreadPoolExecutor(
            10,
            20,·
            1000,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(50),
            Executors.defaultThreadFactory(),
            new DiscardPolicy());

核心数与最大线程数

创建线程池时,核心线程数(corePoolSize)和最大线程数(maximumPoolSize)的设置对线程池的性能和资源利用有很大影响。

什么时候设置核心数与最大线程数一样呢?

在某些需要快速响应并且并发量高的应用场景中,可以设置核心线程数和最大线程数相同,以确保任务能够立即得到处理。例如,在线客服系统或者实时交易系统,任务一旦产生就需要立即处理,这时设置相同的核心线程数和最大线程数能够保证任务的快速处理。

什么时候又不需要设置一样大呢?

在某些应用场景中,任务的到达可能会出现波动,有时任务量很大,有时任务量较小。在这种情况下,可以设置较大的最大线程数,以便在任务量较大时能够创建更多的线程来处理任务。而核心线程数可以设置得较小,以保持线程池在空闲时占用较少的资源。

阻塞队列容量

线程池还需要配置一个阻塞队列,而阻塞队列的容量大小配置同样值得考虑。

假设现在创建了一个核心线程为10的线程,任务执行耗时需要1s,这就意味着1s可以同时处理10个任务,当我们设置阻塞队列容量为50,在容量填满的时候,最后10个任务就需要等待4s后才可以执行,再加上执行的1s,意味着这10个任务就需要5s之后才能执行完成。 这5s的耗时就是需要考虑的时间,如果项目中不允许耗时如此严重的任务存在,就需要缩小我们的容量来减少任务等待时间。

如果任务数超过了50,达到51个,那第51个会看当先线程数是否达到最大线程数,没达到就创建新线程执行,达到了就走拒绝策略,没有影响到队列中的等待任务。

相关推荐
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
杜杜的man5 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
java小吕布5 小时前
Java中Properties的使用详解
java·开发语言·后端
2401_857610036 小时前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
杨哥带你写代码8 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_8 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
背水8 小时前
初识Spring
java·后端·spring
晴天飛 雪9 小时前
Spring Boot MySQL 分库分表
spring boot·后端·mysql
weixin_537590459 小时前
《Spring boot从入门到实战》第七章习题答案
数据库·spring boot·后端