关于Java线程池

Java 线程池参数设置的依据

ThreadPoolExecutor 的核心构造参数为例:

public ThreadPoolExecutor(
    int corePoolSize,       // 核心线程数
    int maximumPoolSize,    // 最大线程数
    long keepAliveTime,     // 非核心线程空闲存活时间
    TimeUnit unit,          // 存活时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory,        // 线程工厂
    RejectedExecutionHandler handler    // 拒绝策略
)

1. corePoolSize(核心线程数)

依据:任务类型 + CPU 核数

CPU 密集型任务(计算、加密、压缩等)

corePoolSize = CPU 逻辑核数 + 1
  • 原因:CPU 密集型任务一直占用 CPU,线程数超过核数反而会增加上下文切换开销
  • +1 的原因:当偶尔某个线程因缺页中断等原因暂停时,多出的那个线程可以顶上,避免 CPU 空转

IO 密集型任务(网络请求、数据库、文件读写等)

corePoolSize = CPU 逻辑核数 × 2
// 或者更精确地:
corePoolSize = CPU 逻辑核数 × (1 + IO 等待时间 / CPU 计算时间)
  • 原因:IO 操作期间线程不占用 CPU(处于 WAITING/TIMED_WAITING 状态),需要更多线程来填补等待空隙
  • IO 等待比例越高,需要的线程越多

混合型任务

corePoolSize = CPU 逻辑核数 × (1 + IO 等待时间 / CPU 计算时间)
  • 关键是估算 CPU 计算时间与 IO 等待时间的比例
  • 可以通过 Profiling 工具(如 Arthas、JFR)观测

2. maximumPoolSize(最大线程数)

依据:系统资源上限 + 任务突发量

场景

推荐设置

依据

CPU 密集型

= corePoolSize

线程数过多无收益,反而增加调度开销

IO 密集型

= corePoolSize × (1.5 ~ 2)

应对突发流量,留出弹性空间

有明确 SLA 要求

根据压测结果反推

保证在最大负载下仍满足响应时间要求

核心原则:最大线程数不是越大越好。线程数过多 → 内存占用高(每个线程约 1MB 栈空间)→ 上下文切换频繁 → 性能下降


3. keepAliveTime + unit(空闲存活时间)

依据:流量波动特征

场景

推荐设置

依据

流量波动大

60秒 ~ 120秒

突发后快速回收闲置线程,释放资源

流量较平稳

300秒 ~ 600秒

避免频繁创建/销毁线程

极端节省资源

30秒

尽快回收,但不至于太频繁创建

  • 核心线程默认不会回收,如需回收可设置:executor.allowCoreThreadTimeOut(true)
  • 需要权衡:时间太短 → 突发来时频繁创建线程;时间太长 → 闲置线程浪费内存

4. workQueue(任务队列)

依据:任务特征 + 内存约束 + 延迟要求

队列类型

特点

适用场景

LinkedBlockingQueue(无界)

队列无限大,maximumPoolSize 基本失效

不推荐,可能导致 OOM

LinkedBlockingQueue(有界)

队列有上限,超出后创建非核心线程

IO 密集型,允许一定缓冲

ArrayBlockingQueue(有界)

有界,公平/非公平可选

需要严格控制内存时

SynchronousQueue

不存储任务,直接交给线程

CPU 密集型或低延迟要求,任务不能排队

PriorityBlockingQueue

按优先级排序

任务有优先级区分时

队列长度依据

队列长度 ≈ 每秒任务 arrivals × 可接受的最大等待时间

例如:每秒 1000 个请求,可接受最大等待 2 秒 → 队列长度 ≈ 2000


5. handler(拒绝策略)

依据:业务语义

策略

行为

适用场景

AbortPolicy(默认)

抛 RejectedExecutionException

需要感知丢弃,及时告警

CallerRunsPolicy

由提交任务的线程自己执行

不想丢弃任务,且可接受降速

DiscardPolicy

静默丢弃

可容忍数据丢失(如日志采集)

DiscardOldestPolicy

丢弃队列最老的任务

新任务优先级更高

自定义

记录日志 + 持久化 + 告警

生产环境推荐


实际生产中的推荐做法

第一步:用公式算初值

int cores = Runtime.getRuntime().availableProcessors();

// CPU 密集型
int coreSize = cores + 1;
int maxSize  = cores + 1;

// IO 密集型
int coreSize = cores * 2;
int maxSize  = cores * 4;

第二步:压测验证

  • 逐步加压,观察 吞吐量(TPS)响应时间(RT)CPU 利用率
  • 找到性能拐点,在拐点附近取值

第三步:运行时监控调整

  • 监控线程池活跃度、队列堆积、拒绝次数
  • 根据线上实际表现再微调

简明速查表

                    CPU 密集型          IO 密集型
corePoolSize       核数+1             核数×2 ~ 核数×(1+W/C)
maximumPoolSize    = corePoolSize      corePoolSize × 1.5~2
keepAliveTime      60s                 60s~120s
workQueue          SynchronousQueue    LinkedBlockingQueue(有界)
拒绝策略            CallerRunsPolicy    CallerRunsPolicy + 告警

W/C = IO 等待时间 / CPU 计算时间,可通过性能分析工具测定

最关键的一点:公式只是起点,压测才是依据。任何不结合实际负载和压测数据的参数设定都是不靠谱的。

posted @ 2022-04-08 17:50  webzd  阅读(53)  评论(0)    收藏  举报