自定义线程池线程数量设置

文章讲述了CPU密集型和IO密集型任务的特性,以及如何根据这些特性配置线程池。对于CPU密集型任务,线程数应设为CPU核心数,以最大化CPU效率;而对于IO密集型任务,线程数通常是CPU核心数的两倍,以利用CPU在IO等待期间的空闲时间。线程池的配置应考虑任务性质和系统资源,以优化性能。

一:CPU密集型:

定义:CPU密集型也是指计算密集型,大部分时间用来做计算逻辑判断等CPU动作的程序称为CPU密集型任务。该类型的任务需要进行大量的计算,主要消耗CPU资源。 这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

特点

01:CPU 使用率较高(也就是经常计算一些复杂的运算,逻辑处理等情况)非常多的情况下使用

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

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

代码示例

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中)
        }
    }
}

二:IO密集型:

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

我们所接触到的 IO ,大致可以分成两种:磁盘 IO和网络 IO。

  1. 磁盘 IO ,大多都是一些针对磁盘的读写操作,最常见的就是文件的读写,假如你的数据库、 Redis 也是在本地的话,那么这个也属于磁盘 IO。
  2. 网络 IO ,这个应该是大家更加熟悉的,我们会遇到各种网络请求,比如 http 请求、远程数据库读写、远程 Redis 读写等等。

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

既然这样,IO 密集型任务其实就有很大的优化空间了(毕竟存在等待):

    CPU 使用率较低,程序中会存在大量的 I/O 操作占用时间,导致线程空余时间很多,所以通常就需要开CPU核心数两倍的线程。当线程进行 I/O 操作 CPU 空闲时,线程等待时间所占比例越高,就需要越多线程,启用其他线程继续使用 CPU,以此提高 CPU 的使用率;线程 CPU 时间所占比例越高,需要越少的线程,这一类型在开发中主要出现在一些计算业务频繁的逻辑中。
  代码示例:

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中)
        }
    }
}

接下来我们进行一一分析:

  1. 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

  2. 并发不高、任务执行时间长的业务这就需要区分开看了:
         a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务
         b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,线程池中的线程数设置得少一些,减少线程上下文的切换

  3. 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,我们的项目使用的时redis作为缓存(这类非关系型数据库还是挺好的)。增加服务器是第二步(一般政府项目的首先,因为不用对项目技术做大改动,求一个稳,但前提是资金充足),至于线程池的设置,设置参考 2 。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件(任务时间过长的可以考虑拆分逻辑放入队列等操作)对任务进行拆分和解耦。

(其实从一二可以看出无论并发高不高,对于业务中是否是cpu密集还是I/O密集的判断都是需要的当前前提是你需要优化性能的前提下)

三.:总结:

  1. 一个计算为主的程序(CPU密集型程序),多线程跑的时候,可以充分利用起所有的 CPU 核心数,比如说 8 个核心的CPU ,开8 个线程的时候,可以同时跑 8 个线程的运算任务,此时是最大效率。但是如果线程远远超出 CPU 核心数量,反而会使得任务效率下降,因为频繁的切换线程也是要消耗时间的。因此对于 CPU 密集型的任务来说,线程数等于 CPU 数是最好的了。
  2. 如果是一个磁盘或网络为主的程序(IO密集型程序),一个线程处在 IO 等待的时候,另一个线程还可以在 CPU 里面跑,有时候 CPU 闲着没事干,所有的线程都在等着 IO,这时候他们就是同时的了,而单线程的话此时还是在一个一个等待的。我们都知道 IO 的速度比起 CPU 来是很慢的。此时线程数等于CPU核心数的两倍是最佳的。
<think>嗯,用户提到自定义线程池时最大线程数不生效,这确实是个常见的问题。首先,我需要回忆一下线程池的工作原理。线程池的核心参数包括核心线程数、最大线程数和任务队列。当任务提交时,线程池的处理流程通常是这样的:先使用核心线程处理任务,如果核心线程都在忙,就把任务放到队列里,队列满了才会创建新线程,直到达到最大线程数。如果超过最大线程数,就会执行拒绝策略。 所以,用户的问题可能出在任务队列的设置上。比如,如果用户使用了无界队列(比如LinkedBlockingQueue没有指定容量),那么队列永远不会满,所以线程池就不会创建超过核心线程数的线程,最大线程数自然不生效。这时候需要检查队列的类型和容量。 另外,还有可能是线程池的任务提交顺序或者拒绝策略的问题。比如,如果拒绝策略被设置为丢弃任务或者由调用者线程执行,可能在队列未满的情况下,新任务被拒绝,导致无法触发创建新线程。这时候需要检查拒绝策略的设置。 还有一种情况是,线程池线程工厂(ThreadFactory)可能有问题,导致无法创建新线程,比如抛出异常或者返回null。这时候需要检查线程工厂的实现是否正确。 另外,线程空闲后的存活时间(keepAliveTime)可能设置得不够长,导致线程在任务处理完毕后很快被回收,但这种情况不会影响最大线程数的创建,只是线程数量会动态调整。 所以,总结可能的解决步骤:首先检查任务队列是否有界,然后确认拒绝策略是否合理,接着查看线程工厂是否正常,最后确认线程池参数配置是否正确。通过这些步骤,应该能找到问题所在。</think>好的,我分步骤帮你分析自定义线程池中最大线程数不生效的常见原因及解决方案: --- ### **1. 核心参数关系理解** 线程池行为由三个关键参数共同决定: - **corePoolSize**(核心线程数) - **maxPoolSize**(最大线程数) - **workQueue**(任务队列) --- ### **2. 执行流程验证** 当任务提交时,线程池按以下顺序处理: 1. **核心线程优先**:当前线程数 < corePoolSize → 立即创建新线程 2. **入队检测**:当前线程数 ≥ corePoolSize → 尝试将任务放入队列 3. **扩容检测**:当队列已满 → 创建新线程直至达到 maxPoolSize 4. **拒绝策略**:当线程数达到 maxPoolSize 且队列已满 → 触发拒绝策略 --- ### **3. 常见问题排查** #### **情况1:使用了无界队列** - **现象**:队列永远不会满 → 永远无法触发扩容 - **错误示例**: ```java new ThreadPoolExecutor( 2, // corePoolSize 10, // maxPoolSize 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>() // 无界队列! ); ``` - **解决方案**:改用有界队列(如 `new ArrayBlockingQueue<>(100)`) #### **情况2:任务未达队列容量** - **现象**:队列未满时,新任务持续入队而非创建新线程 - **验证方法**:监控队列长度(通过 `getQueue().size()`) #### **情况3:特殊队列策略** - **SynchronousQueue**(直接传递队列): - **特点**:不存储任务,要求必须有可用线程 - **结果**:当核心线程忙时立即扩容,此时 maxPoolSize 生效 #### **情况4:线程存活时间过短** - **参数**:`keepAliveTime`(非核心线程空闲存活时间) - **影响**:不影响最大线程数的创建,只影响线程回收速度 --- ### **4. 验证代码示例** ```java ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // core 5, // max 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列容量3 ); // 提交10个任务观察行为 for (int i = 0; i < 10; i++) { executor.execute(() -> { try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println("当前线程数: " + executor.getPoolSize()); } ``` --- ### **5. 预期结果分析** | 提交顺序 | 队列状态 | 线程数变化 | |----------|-----------------|--------------------| | 任务1-2 | 核心线程处理 | 线程数=2 | | 任务3-5 | 入队(队列满) | 线程数保持2 | | 任务6-8 | 触发扩容至max=5 | 线程数逐步增至5 | | 任务9-10 | 触发拒绝策略 | 抛出RejectedExecutionException | --- ### **6. 配置建议** - 根据业务场景选择队列类型: - **CPU密集型**:建议使用有界队列 + 较小maxPoolSize - **IO密集型**:可适当增大maxPoolSize - 推荐使用监控工具(如Spring Boot Actuator)观察线程池状态
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值