• 工作中何如来合理分配核心线程数?



    一 回顾

    1.1 使用线程池的优点

    • 降低资源消耗:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,通过重复利用已创建的线程可以降低线程创建和销毁造成的消耗。
    • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
    • 提高线程的可管理性:线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务的数量等。通过线程池可以对线程资源进行统一的分配、监控和调优。

    注意:但是在生产环境中,不合理的分配线程数,可能会造成严重的生成事故

    1.2 任务类型

    1. IO密集型任务此类任务主要是执行IO操作。由于执行IO操作的时间较长,导致CPU的利用率不高,这类任务CPU常处于空闲状态。Netty的IO读写操作为此类任务的典型例子。
    2. CPU密集型任务此类任务主要是执行计算任务。由于响应时间很快,CPU一直在运行,这种任务CPU的利用率很高。
    3. 混合型任务此类任务既要执行逻辑计算,又要进行IO操作(如RPC调用、数据库访问)。相对来说,由于执行IO操作的耗时较长(一次网络往返往往在数百毫秒级别),这类任务的CPU利用率也不是太高。Web服务器的HTTP请求处理操作为此类任务的典型例子。

    1.3 IO密集型任务确定线程数

    • 由于IO密集型任务的CPU使用率较低,导致线程空余时间很多,因此通常需要开CPU核心数两倍的线程。
    • 当IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。

    Netty 核心线程数

         //多线程版本Reactor实现类
         public abstract class MultithreadEventLoopGroup extends
                 MultithreadEventExecutorGroup implements EventLoopGroup {
         
             //IO事件处理线程数
             private static final int DEFAULT_EVENT_LOOP_THREADS;
         
             //IO事件处理线程数默认值为CPU核数的两倍
             static {
                 DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
                          SystemPropertyUtil.getInt("io.netty.eventLoopThreads",
                          Runtime.getRuntime().availableProcessors() * 2));
             }
         
             /**
              *构造器
              */
             protected MultithreadEventLoopGroup(int nThreads, 
                         ThreadFactory threadFactory, Object... args) {
                 super(nThreads == 0?
                          DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
             }
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    总结

    如果是IO密集型任务核心数=CPU核数的两倍

    参考案例

    
         // 省略import
         public class ThreadUtil
         {
             //CPU核数
             private static final int CPU_COUNT =
                                             Runtime.getRuntime().availableProcessors();
             //IO处理线程数
             private static final int IO_MAX = Math.max(2, CPU_COUNT * 2);
             /**
              * 空闲保活时限,单位秒
              */
             private static final int KEEP_ALIVE_SECONDS = 30;
             /**
              * 有界队列size
              */
             private static final int QUEUE_SIZE = 128;
             //懒汉式单例创建线程池:用于IO密集型任务
             private static class IoIntenseTargetThreadPoolLazyHolder
             {
                 //线程池: 用于IO密集型任务
                 private static final ThreadPoolExecutor EXECUTOR =
                                                                    new ThreadPoolExecutor(
                         IO_MAX,  //CPU核数*2
                         IO_MAX,  //CPU核数*2
                         KEEP_ALIVE_SECONDS,
                         TimeUnit.SECONDS,
                         new LinkedBlockingQueue(QUEUE_SIZE),
                         new CustomThreadFactory("io"));
         
                 static
                 {
                     EXECUTOR.allowCoreThreadTimeOut(true);
                     //JVM关闭时的钩子函数
                     Runtime.getRuntime().addShutdownHook(
                             new ShutdownHookThread("IO密集型任务线程池",
                                                                         new Callable<Void>()
                             {
                                 @Override
                                 public Void call() throws Exception
                                 {
                                     //优雅地关闭线程池
                                     shutdownThreadPoolGracefully(EXECUTOR);
                                     return null;
                                 }
                             }));
                 }
             }
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    1.4 CPU密集型任务确定线程数

    CPU密集型任务也叫计算密集型任务,其特点是要进行大量计算而需要消耗CPU资源,比如计算圆周率、对视频进行高清解码等。CPU密集型任务虽然也可以并行完成,但是并行的任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以要最高效地利用CPU,CPU密集型任务并行执行的数量应当等于CPU的核心数。

    案例

    	// 省略import
         public class ThreadUtil
         {
             //CPU核数
             private static final int CPU_COUNT =
                                             Runtime.getRuntime().availableProcessors();
         
             private static final int MAXIMUM_POOL_SIZE = CPU_COUNT;
         
             //懒汉式单例创建线程池:用于CPU密集型任务
             private static class CpuIntenseTargetThreadPoolLazyHolder
             {
                 //线程池:用于CPU密集型任务
                 private static final ThreadPoolExecutor EXECUTOR = 
                                                                   new ThreadPoolExecutor(
                         MAXIMUM_POOL_SIZE,
                         MAXIMUM_POOL_SIZE,
                         KEEP_ALIVE_SECONDS,
                         TimeUnit.SECONDS,
                         new LinkedBlockingQueue(QUEUE_SIZE),
                         new CustomThreadFactory("cpu"));
         
                 static
                 {
                     EXECUTOR.allowCoreThreadTimeOut(true);
                     //JVM关闭时的钩子函数
                     Runtime.getRuntime().addShutdownHook(
                             new ShutdownHookThread("CPU密集型任务线程池",
                                                                       new Callable<Void>()
                             {
                                 @Override
                                 public Void call() throws Exception
                                 {
                                     //优雅地关闭线程池
                                     shutdownThreadPoolGracefully(EXECUTOR);
                                     return null;
                                 }
                             }));
                 }
             }
             // 省略不相干代码
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1.5 混合型任务确定线程数

    混合型任务既要执行逻辑计算,又要进行大量非CPU耗时操作(如RPC调用、数据库访问、网络通信等),所以混合型任务CPU的利用率不是太高,非CPU耗时往往是CPU耗时的数倍。比如在Web应用中处理HTTP请求时,一次请求处理会包括DB操作、RPC操作、缓存操作等多种耗时操作。一般来说,一次Web请求的CPU计算耗时往往较少,大致在100~500毫秒,而其他耗时操作会占用500~1000毫秒,甚至更多的时间。

         最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) * CPU核数
    
    • 1

    等待时间所占的比例越高,需要的线程就越多;CPU耗时所占的比例越高,需要的线程就越少。

    获取所有线程的CPU执行时间

    ThreadMXBean tmbean = ManagementFactory.getThreadMXBean();
    tmbean.setThreadContentionMonitoringEnabled(true);
    long[] allThread = tmbean.getAllThreadIds();
    
    • 1
    • 2
    • 3

    获取到id数组之后,遍历线程id,通过getThreadCpuTime(long id)等获取线时间

    获取等待、阻塞时间等

    ThreadInfo info = tmbean.getThreadInfo(threadId);
    
    • 1
    package Time;
    
    import java.lang.management.ManagementFactory;
    import java.lang.management.ThreadInfo;
    import java.lang.management.ThreadMXBean;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/11/1 13:45
     * @version: 1.0
     */
    public class TimeThreadTest {
        public static void main(String[] args) {
            ThreadMXBean bean = ManagementFactory.getThreadMXBean();
            bean.setThreadContentionMonitoringEnabled(true);
            long[] allThread = bean.getAllThreadIds();
            if (allThread.length > 0) {
                for (long threadId : allThread) {
                    ThreadInfo info = bean.getThreadInfo(threadId);
                    Long userTime = info.getWaitedTime();
                    System.out.println("ThreadId:" + info.getThreadId() +" "+ "ThreadName:" + info.getThreadName() +"  "+ "ThreadState:" + info.getThreadState() +"  "+"userTime:" + userTime.toString());
                }
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image.png

    用例

    
         // 省略import
         public class ThreadUtil
         {
             private static final int MIXED_MAX = 128;  //最大线程数
             private static final String MIXED_THREAD_AMOUNT = "mixed.thread.amount";
         
             //懒汉式单例创建线程池:用于混合型任务
             private static class MixedTargetThreadPoolLazyHolder
             {
                 //首先从环境变量 mixed.thread.amount 中获取预先配置的线程数
                 //如果没有对 mixed.thread.amount进行配置,就使用常量 MIXED_MAX作为线程数
                 private static final int max = 
                                   (null != System.getProperty(MIXED_THREAD_AMOUNT)) ?
                         Integer.parseInt(System.getProperty(MIXED_THREAD_AMOUNT))
                                                                               : MIXED_MAX;
                 //线程池:用于混合型任务
                 private static final ThreadPoolExecutor EXECUTOR = 
                                                                   new ThreadPoolExecutor(
                         max,
                         max,
                         KEEP_ALIVE_SECONDS,
                         TimeUnit.SECONDS,
                         new LinkedBlockingQueue(QUEUE_SIZE),
                         new CustomThreadFactory("mixed"));
         
                 static
                 {
                     EXECUTOR.allowCoreThreadTimeOut(true);
                     //JVM关闭时的钩子函数
                     Runtime.getRuntime().addShutdownHook(
                         new ShutdownHookThread("混合型任务线程池", new Callable<Void>()
                     {
                         @Override
                         public Void call() throws Exception
                         {
                             //优雅地关闭线程池
                             shutdownThreadPoolGracefully(EXECUTOR);
                             return null;
                         }
                     }));
                 }
             }
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
  • 相关阅读:
    C++三大特性之一:继承
    彻底理解Java并发:Java线程
    Vim 常用指令
    makefile记录
    什么是 Spring Boot Starter?(InsCode AI 创作助手)
    【目标检测】从头到尾教你安装MMDetection(超详细版本)
    NVM安装node后提示没有对应npm包(即:无法将“npm”项识别为 cmdlet、函数、脚本文件)
    Android基础第十天 | 字节跳动第四届青训营笔记
    求解置换流水车间调度问题PFSP的关键路径-附Matlab源码
    模块化讲解
  • 原文地址:https://blog.csdn.net/weixin_44451022/article/details/127641064