【面试实战】# 并发编程之线程池设置实战

乌市泽哥  金牌会员 | 2024-6-19 13:50:01 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 899|帖子 899|积分 2697

1.先相识线程池的几个参数含义

corePoolSize (核心线程池巨细):

  • 作用: 指定了线程池维护的核心线程数量,即使这些线程处于空闲状态,它们也不会被回收。
  • 用途: 核心线程用于处理长期的任务,保持最低的线程数量,以淘汰线程的创建和销毁的开销。
maximumPoolSize (最大线程池巨细):

  • 作用: 指定了线程池中允许的最大线程数。超过这个数量的线程将不会被创建。
  • 用途: 限制了线程池的巨细,以防止资源耗尽。
keepAliveTime (线程空闲时间):

  • 作用: 当线程数超过 corePoolSize 时,多余的线程在空闲时间超过指定时间后将会被终止和回收。
  • 用途: 用于回收不再必要的线程,低落资源消耗。只对超过 corePoolSize 的线程起作用。
unit (时间单位):

  • 作用: 与 keepAliveTime 一起使用,指定线程空闲时间的时间单位(如秒、毫秒)。
  • 用途: 定义 keepAliveTime 的时间单位。
workQueue (任务队列):

  • 作用: 用于保存等候执行的任务的队列。
  • 用途管理任务的排队和处理方式,不同的队列类型可以影响线程池的举动。

    • 常见的队列类型有:

      • SynchronousQueue: 不存储任务,任务直接交给线程执行。如果没有空闲线程,则创建新线程。
      • LinkedBlockingQueue: 无界队列,可以存储任意多的任务。只有在任务队列为空时,才会创建新线程。
      • ArrayBlockingQueue: 有界队列,存储固定数量的任务,当队列满时,任务将被拒绝。


threadFactory (线程工厂):

  • 作用: 用于创建线程的工厂,可以定制线程的创建,好比设置线程名、优先级等。
  • 用途: 统一管理线程的创建细节,有助于调试和监控。
handler (饱和策略/拒绝策略):

  • 作用: 当任务无法提交给线程池(例如线程池已满且任务队列已满)时,如何处理新任务。
  • 用途定义任务无法被执行时的处理方式。

    • 常见策略有:

      • AbortPolicy: 抛出 RejectedExecutionException 异常。
      • CallerRunsPolicy: 由调用者线程执行该任务。
      • DiscardPolicy: 抛弃新提交的任务。
      • DiscardOldestPolicy: 抛弃队列中最旧的任务。


2.调整线程池设置应对高并发(常规操作)

为了应对高并发的需求,可以考虑以下调整:

  • 增大 corePoolSize 和 maximumPoolSize:

    • 增长核心线程和最大线程数可以提高线程池的并发处理本领,淘汰任务的等候时间。

  • 调整 keepAliveTime 和 unit:

    • 淘汰 keepAliveTime 可以更快地回收闲置线程,开释资源。相反,增长 keepAliveTime 实用于任务隔断较长的场景,以避免频繁创建和销毁线程。

  • 选择合适的 workQueue:

    • 使用 SynchronousQueue 可以在任务许多但线程数不足时迅速增长线程数。
    • 使用 LinkedBlockingQueue 可以应对任务队列过长的标题,但大概导致线程数不会增长到最大。
    • 使用 ArrayBlockingQueue 得当在任务数有限的场景,防止资源耗尽。

  • 合理设置 handler:

    • 根据系统需求选择得当的拒绝策略。好比,在希望任务尽量被处理时使用 CallerRunsPolicy,在任务不能丢失时选择 AbortPolicy。

  • 优化 threadFactory:

    • 使用自定义的线程工厂设置线程名、优先级、保卫线程等,提高线程管理的清晰度和系统稳定性。

  • 监控和调整:

    • 定期监控线程池的性能指标,如任务队列长度、线程使用率等,并根据现实环境动态调整参数设置。

  1. // 创建线程池
  2. ThreadPoolExecutor executor = new ThreadPoolExecutor(
  3.     10,                   // corePoolSize
  4.     50,                   // maximumPoolSize
  5.     60,                   // keepAliveTime
  6.     TimeUnit.SECONDS,     // keepAliveTime's unit
  7.     new LinkedBlockingQueue<>(100), // workQueue
  8.     Executors.defaultThreadFactory(), // threadFactory
  9.     new ThreadPoolExecutor.AbortPolicy() // handler
  10. );
  11. // 提交任务
  12. executor.submit(() -> {
  13.     // Task implementation
  14. });
  15. // 关闭线程池
  16. executor.shutdown();
复制代码
3.IO密集型、CPU密集型任务的合理设置(生产常用)

3.1 IO密集型任务

IO密集型任务:(例如网络操作、文件读写)通常不必要大量的CPU时间,但大概会等候IO操作的完成。为了有效使用系统资源,可以设置更多的线程来掩盖IO操作的等候时间。
设置建议:

  • corePoolSizemaximumPoolSize:

    • 建议的线程数通常远超过 CPU 核心数,因为线程在等候IO操作时不会占用CPU。可以使用 (CPU 核心数 * 2) 或更多,甚至是 (CPU 核心数 * 2) + 1 这种履历值。
    • 如果线程数太少,CPU资源大概未能充分使用。太多的线程大概会导致线程上下文切换的开销。

  • keepAliveTimeunit:

    • 得本地增长 keepAliveTime,让线程在空闲时保存一段时间,以便在短时间内有任务到达时无需重新创建线程。

  • workQueue:

    • LinkedBlockingQueue 是常见选择,因为它可以有效处理大量任务,而不必要频繁地创建和销毁线程。
    • SynchronousQueue 也可以用于高并发IO场景,确保任务直接交给线程执行,迅速响应。

示例:
  1. int numCores = Runtime.getRuntime().availableProcessors();
  2. ThreadPoolExecutor ioBoundExecutor = new ThreadPoolExecutor(
  3.     numCores * 2,                // corePoolSize
  4.     numCores * 2 + 1,            // maximumPoolSize
  5.     60L,                         // keepAliveTime
  6.     TimeUnit.SECONDS,            // keepAliveTime's unit
  7.     new LinkedBlockingQueue<>(), // workQueue
  8.     Executors.defaultThreadFactory(), // threadFactory
  9.     new ThreadPoolExecutor.CallerRunsPolicy() // handler
  10. );
复制代码
3.2 CPU密集型任务

CPU密集型任务:(例如计算密集的操作、数据处理)主要消耗 CPU 资源,因此线程数应该与 CPU 核心数相匹配,以避免过分的线程上下文切换和资源竞争。
设置建议:

  • corePoolSizemaximumPoolSize:

    • 通常设置为 CPU 核心数 或 CPU 核心数 + 1。
    • 过多的线程大概导致频繁的上下文切换,低落性能。

  • keepAliveTimeunit:

    • keepAliveTime 通常设置较短,得当及时回收空闲线程。

  • workQueue:

    • SynchronousQueueArrayBlockingQueue 是不错的选择,可以避免任务堆积,确保线程数控制在合理范围内。

示例:
  1. int numCores = Runtime.getRuntime().availableProcessors();
  2. ThreadPoolExecutor cpuBoundExecutor = new ThreadPoolExecutor(
  3.     numCores,                    // corePoolSize
  4.     numCores + 1,                // maximumPoolSize
  5.     30L,                         // keepAliveTime
  6.     TimeUnit.SECONDS,            // keepAliveTime's unit
  7.     new SynchronousQueue<>(),    // workQueue
  8.     Executors.defaultThreadFactory(), // threadFactory
  9.     new ThreadPoolExecutor.AbortPolicy() // handler
  10. );
复制代码
3.3 关键考虑因素


  • 系统资源和负载:

    • 监控系统的现实负载和资源使用环境,定期调整设置。

  • 任务特性:

    • 根据任务的性质(长任务、短任务、IO 密集型、CPU 密集型)选择合适的线程池设置。

  • 壅闭时间:

    • 对于 IO 密集型任务,理解和分析任务的壅闭时间,并根据其壅闭时间设置合适的线程池巨细。

  • 拒绝策略:

    • 合理选择拒绝策略(如 AbortPolicy, CallerRunsPolicy),确保系统在负载过高时能平稳处理任务。

4.专业级线程池设置(大厂规范)

4.1 线程池巨细的计算公式

IO 密集型任务

对于IO密集型任务,可以使用以下公式计算得当的线程池巨细:


  • N_threads: 保举的线程池巨细
  • N_cores: CPU核心数
  • W: 任务的等候时间(包括IO操作的等候时间)
  • C: 任务的计算时间
  • U: 盼望的CPU使用率,通常设为0.8~0.9,避免CPU负载过高(0 < U < 1)
解释: 公式中的 W/C反映了IO操作占用的时间比,1 - U 是为了预留肯定的CPU资源。
示例:
假设有一个任务,CPU核心数为8,IO等候时间为200ms,计算时间为100ms,盼望的CPU使用率为80%,则保举的线程池巨细为:

这意味着你大概必要设置大约120个线程来处理IO密集型任务。
CPU 密集型任务

对于CPU密集型任务,线程池的巨细通常可以通过以下公式估算:

在CPU密集型场景下,由于 W 很小或靠近于零,因此公式通常简化为:

示例:
假设有一个任务,CPU核心数为8,计算时间大部分占用时间,等候时间可以忽略不计,则保举的线程池巨细为:

5.根据TPS和QPS进行线程池计算(生产常用)

其实和4的公式差不多
5.1 基础概念


  • TPS (Transactions Per Second): 每秒系统处理的变乱数量。这通常用于描述系统处理更复杂的业务逻辑的本领。
  • QPS (Queries Per Second): 每秒系统处理的查询数量,通常用于权衡服务端API或数据库的查询处理本领。
  • 响应时间: 单个请求或变乱的均匀处理时间。
5.2 公式:



  • N_threads: 保举的线程池巨细
  • Q: 每秒的请求数(TPS 或 QPS)
  • R: 均匀响应时间(秒)
  • U: 系统盼望的CPU使用率(< 1, 通常为80%~90%)
解释: 公式描述了在满足特定吞吐量和响应时间的环境下,必要的线程数,预留了一部分CPU资源以防过载。
5.3 IO密集型、CPU密集型任务选择

这里我们主要举例阐明IO密集型任务
因为:CPU密集型任务主要消耗CPU资源,线程数靠近CPU核心数就足够,可以加一个额外的线程来处理。Nthreads=Ncores+1
IO密集型:

公式:

阐明: 由于IO密集型任务在等候IO时不会占用CPU,因此线程数可以较高,实用于处理高并发的IO操作。
示例:
假设系统必要处理每秒500个请求(Q = 500),每个请求的均匀响应时间为0.2秒,系统盼望的CPU使用率为80%(U = 0.8):

这意味着你大概必要大约500个线程来处理这些IO密集型请求。
示例代码:
  1. int qps = 500;
  2. double responseTime = 0.2;
  3. double targetUtilization = 0.8;
  4. int nThreads = (int) (qps * responseTime / (1 - targetUtilization));
  5. ThreadPoolExecutor ioBoundExecutor = new ThreadPoolExecutor(
  6.     nThreads,                // corePoolSize
  7.     nThreads,                // maximumPoolSize
  8.     60L,                     // keepAliveTime
  9.     TimeUnit.SECONDS,        // keepAliveTime's unit
  10.     new LinkedBlockingQueue<>(), // workQueue
  11.     Executors.defaultThreadFactory(), // threadFactory
  12.     new ThreadPoolExecutor.CallerRunsPolicy() // handler
  13. );
复制代码
6.总结


  • IO密集型任务: 使用公式
    计算线程池巨细。
  • CPU密集型任务: 使用公式
    计算线程池巨细。
  • 混合型任务: 综合IO和CPU的公式进行计算和调整。


    • W: 均匀等候时间
    • C: 均匀计算时间

  • 现实应用: 根据QPS或TPS、响应时间、盼望的CPU使用率等参数进行计算,并定期监控系统负载进行调整。
合理的线程池设置可以显著提升系统的处理本领和资源使用率,因此根据具体需求和系统指标进行精细设置是至关告急的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

乌市泽哥

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表