【架构师面试-JUC并发编程-12】-基于源码分析-线程池合适的线程数量是多少

本文探讨了如何根据任务类型调整线程池中的线程数量以优化性能。对于CPU密集型任务,最佳线程数通常是CPU核心数的1到2倍,以避免过度的上下文切换。而对于IO密集型任务,线程数通常设置为CPU核心数的多倍,利用CPU空闲时间处理更多任务。文中还给出了通用公式:线程数=CPU核心数*(1+IO耗时/CPU耗时),并建议通过压测和监控来确定最合适的线程数量。
摘要由CSDN通过智能技术生成

本章主要讨论线程池合适的线程数量是多少,以及 CPU 核心数和线程数的关系。我们调整线程池中的线程数量的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能。在实际工作中,我们需要根据任务类型的不同选择对应的策略。

1:CPU密集型

1:定义

CPU密集型也是指计算密集型,大部分时间用来做计算逻辑判断等CPU动作的程序称为CPU密集型任务。该类型的任务需要进行大量的计算,主要消耗CPU资源。 

2:场景

加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。

3:结论

最佳线程数 = CPU 核心数的 1~2 倍

4:特点

CPU 使用率较高非常多的情况下使用。

针对单台机器,最大线程数一般只需要设置为CPU核心数的线程个数就可以了。

这一类型多出现在开发中的一些业务复杂计算和逻辑处理过程中。

5:分析

如果设置过多的线程,实际上并不会起到很好的效果。此时假设我们设置的线程数是 CPU 核心数的 2 倍以上,因为计算机的任务很重,会占用大量的 CPU 资源,所以这是 CPU 每个核心都是满负荷工作,而设置过多的线程数,每个线程都去抢占 CPU 资源,就会产生不必要的上下文切换,反而会造成整体性能的下降。

6:源码分析

package pool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo02 {
    public static void main(String[] args) {
        //自定义线程池! 工作中只会使用 ThreadPoolExecutor

        /**
         * 最大线程该如何定义(线程池的最大的大小如何设置!)
         * 1、CPU  密集型,几核,就是几,可以保持CPU的效率最高!
         */

        //获取电脑CPU核数
        System.out.println(Runtime.getRuntime().availableProcessors());    //8核

        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,                                        //核心线程池大小
                Runtime.getRuntime().availableProcessors(),   //最大核心线程池大小(CPU密集型,根据CPU核数设置)
                3,                                       //超时了没有人调用就会释放
                TimeUnit.SECONDS,                             //超时单位
                new LinkedBlockingDeque<>(3),                 //阻塞队列
                Executors.defaultThreadFactory(),             //线程工厂,创建线程的,一般不用动
                new ThreadPoolExecutor.AbortPolicy());        //银行满了,还有人进来,不处理这个人的,抛出异常

        try {
            //最大承载数,Deque + Max    (队列线程数+最大线程数)
            //超出 抛出 RejectedExecutionException 异常
            for (int i = 1; i <= 9; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();      //(为确保关闭,将关闭方法放入到finally中)
        }
    }
}

2:IO密集型

1:定义

IO密集型任务指任务需要执行大量的IO操作,涉及到网络、磁盘IO操作,对CPU消耗较少,其消耗的主要资源为IO。

2:场景

磁盘 IO ,大多都是一些针对磁盘的读写操作,最常见的就是文件的读写,假如数据库、 Redis 也是在本地的话,那么这个也属于磁盘 IO。

网络 IO ,这个应该是大家更加熟悉的,我们会遇到各种网络请求,比如 http 请求、远程数据库读写、远程 Redis 读写等等。

3:结论

最大线程数一般会大于 CPU 核心数很多倍。

4:特点

并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。

5:分析

因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,可能导致 CPU 资源的浪费。

而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。

我们请求一些数据,由对方将数据写入缓冲区,在这段时间中,需要读取数据的线程根本无事可做,因此可以把 CPU 时间片让出去,直到缓冲区写满。

既然这样,IO 密集型任务其实就有很大的优化空间了。  

6:源码分析

package pool;


import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class Demo02 {
    public static void main(String[] args) {
        //自定义线程池! 工作中只会使用 ThreadPoolExecutor


        /**
         * 最大线程该如何定义(线程池的最大的大小如何设置!)
         * 2、IO   密集型  >判断你程序中十分耗IO的线程
         *      程序    15个大型任务   io十分占用资源!  (最大线程数设置为30)
         *      设置最大线程数为十分耗io资源线程个数的2倍
         */


        //获取电脑CPU核数
        System.out.println(Runtime.getRuntime().availableProcessors());   //8核


        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,                               //核心线程池大小
                16,                     //若一个IO密集型程序有15个大型任务且其io十分占用资源!(最大线程数设置为 2*CPU 数目)
                3,                                //超时了没有人调用就会释放
                TimeUnit.SECONDS,                 //超时单位
                new LinkedBlockingDeque<>(3),     //阻塞队列
                Executors.defaultThreadFactory(),               //线程工厂,创建线程的,一般不用动
                new ThreadPoolExecutor.DiscardOldestPolicy());  //队列满了,尝试和最早的竞争,也不会抛出异常


        try {
            //最大承载数,Deque + Max    (队列线程数+最大线程数)
            //超出 抛出 RejectedExecutionException 异常
            for (int i = 1; i <= 9; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();      //(为确保关闭,将关闭方法放入到finally中)
        }
    }
}

3:通用公式

1:公式

线程数 = CPU 核心数 * (1+ IO 耗时/CPU 耗时)

线程数  = CPU 核心数 * (任务执行时间 / 任务CPU时间)

上面两个公式本质是一样的

通过这个公式,我们可以计算出一个合理的线程数量,如果任务的 IO 耗时时间长,线程数就随之增加,而如果CPU 耗时长,也就是对于我们上面的 CPU 密集型任务,线程数就随之减少。

太少的线程数会使得程序整体性能降低,而过多的线程也会消耗内存等其他资源,所以如果想要更准确的话,可以进行压测,监控 JVM 的线程情况以及 CPU 的负载情况,根据实际情况衡量应该创建的线程数,合理并充分利用资源。

2:CPU时间计算

CPU时间即反映CPU全速工作时完成该进程所花费的时间。

CPU时间计算

CPU TIME =(# of CPU Clock Cycles)x Clock Period// #” 表示消耗的CPU时钟周期个数

= (# of CPU Clock Cycles)/(Clock Frequency)

cpu时间计算 = (#个cpu时钟周期) X 电子脉冲时钟周期  (类似于晶振产生的脉冲,而频率就是单位时间产生脉冲数量 = 1/单个脉冲时钟周期占用时间)

                 =  (#个cpu时钟周期) / 电子脉冲时钟频率

如果对其时间计算有兴趣可以参考文章:cpu时间 / cpu利用率计算 - IMEIXI4EVER - 博客园

3:最大线程数怎么确定

图文版解释

线程池最大线程数量怎么确定_怎么给女朋友讲明白线程池?_武藤 杰洛特的博客-CSDN博客

4:结论

综上所述我们就可以得出以下结论:

线程的 CPU 耗时所占比例越高,就需要越少的线程

线程的 IO 耗时所占比例越高,就需要越多的线程

针对不同的程序,进行对应的实际测试就可以得到最合适的选择

线程数 >= CPU 核心数

5:参考文章

cpu时间 / cpu利用率计算 - IMEIXI4EVER - 博客园

创建自定义线程池(最大线程数该如何设置?) - 你的龙儿 - 博客园

线程池合适的线程数量是多少?_谢文峰的博客-CSDN博客_线程池数量多少合适

线程池最大线程数量怎么确定_怎么给女朋友讲明白线程池?_武藤 杰洛特的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不要迷恋发哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值