线程池核心参数与执行原理
线程池是 Java 并发编程的基石,理解其运作机制对性能调优至关重要。核心参数主要有七个:
- corePoolSize:核心线程数,线程池维持的最小活跃线程数。
- maximumPoolSize:最大线程数量,允许创建的最大线程数(核心 + 救急)。
- keepAliveTime:非核心线程的存活时间,空闲超时后会被回收。
- unit:存活时间的单位。
- workQueue:工作队列,用于缓存待执行任务。当核心线程满时,新任务进入队列。
- threadFactory:线程工厂,用于定制线程名称、是否为守护线程等属性。
- handler:拒绝策略,当队列和线程数都达到上限时的处理方案。
执行流程简述:
- 任务提交后,若当前线程数小于 corePoolSize,则创建新核心线程执行。
- 若核心线程已满,任务进入 workQueue 等待。
- 若队列已满且线程数小于 maximumPoolSize,则创建非核心线程执行。
- 若队列和线程数均达上限,触发 handler 拒绝策略。
常见阻塞队列对比
常用的阻塞队列包括 ArrayBlockingQueue 和 LinkedBlockingQueue,两者底层结构不同导致性能差异明显:
| 特性 | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| 底层结构 | 数组 | 链表 |
| 容量限制 | 强制有界 | 默认无界,可指定容量 |
| 节点创建 | 初始化时创建 | 插入时动态创建 |
| 锁机制 | 两把锁(头尾分离) | 一把锁 |
LinkedBlockingQueue 采用锁分离设计,生产者和消费者互不干扰,适合吞吐量要求较高的场景。
核心线程数如何确定?
没有固定公式,需结合业务指标调整。一般参考以下经验值:
- CPU 密集型任务:计算密集,上下文切换开销大。建议
CPU 核数 + 1。 - IO 密集型任务:读写多,CPU 占用低。建议
CPU 核数 * 2 + 1。 - 高并发短任务:减少线程切换,可适当增加线程数。
实际生产中,应设定预期 CPU 利用率、负载及 GC 频率,通过压测不断微调。
常用线程池类型
JUC 包中的 Executors 提供了四种典型实现:
- FixedThreadPool:固定线程数,无救急线程,使用 LinkedBlockingQueue。适合任务量已知、耗时较长的场景。
- SingleThreadExecutor:单线程池,按序执行。相比普通单线程,线程复用减少了创建销毁开销。
- CachedThreadPool:可缓存线程池,核心为 0,最大为 Integer.MAX_VALUE。适合任务密集但执行时间短的场景,空闲线程 1 分钟后回收。
- ScheduledThreadPool:支持定时或周期性执行任务。
使用场景示例:
- 批量导入:将 PGSQL 数据导入 ES 时,可计算总页数 N,设置 CountdownLatch 计数为 N,创建 N 个线程并行处理,主线程 await 完成后结束。
- 异步调用:搜索记录保存不影响主流程,可通过 @Async 注解配合自定义线程池注入容器实现。
ThreadLocal 与内存泄漏
ThreadLocal 实现了线程间资源隔离,每个线程拥有独立的变量副本。其底层依赖静态内部类 ThreadLocalMap,Key 为 ThreadLocal 实例,Value 为存储的对象。
内存泄漏原因: 在 ThreadLocalMap 中,Key 是弱引用,而 Value 是强引用。GC 时,若 ThreadLocal 对象被回收,Key 变为 null,但 Value 仍被当前线程持有无法释放,形成泄漏。
解决方案: 使用完 ThreadLocal 后,务必手动调用 remove() 方法清除 Entry。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
JVM 虚拟机详解
主要组成部分
- 类加载器:负责将字节码加载到内存。
- 运行时数据区:程序运行的内存空间,包含堆、栈、方法区等。
- 执行引擎:翻译字节码为系统指令并执行。
- 本地方法接口:与本地库交互。
运行时数据区
- 方法区/元空间:存储类信息、常量、静态变量。线程共享,OOM 时报 MetaspaceError。
- 堆:存储对象实例和数组。分新生代(Eden + Survivor)和老年代。OOM 时报 OutOfMemoryError。
- 程序计数器:记录当前线程执行的字节码地址,线程私有。
- 虚拟机栈:存储局部变量、操作数栈、返回地址等。栈帧出栈即释放。
- 本地方法栈:为 Native 方法服务。
注意:栈深度过大抛出 StackOverflowError;扩展栈失败抛出 OOM。
垃圾回收基础
- 判断对象存活:可达性分析算法(从 GCRoot 向下搜索)或引用计数法。
- GCRoot 来源:虚拟机栈引用、本地方法栈引用、方法区静态属性/常量。
垃圾回收算法
- 标记 - 清除:先标记后清除,易产生内存碎片。
- 标记 - 整理:标记后移动存活对象,消除碎片,但效率较低。
- 复制算法:将内存分为两块,每次用一块,GC 时将存活对象复制到另一块。新生代常用此法,内存利用率低但效率高。
分代收集
JVM 将堆分为新生代和老年代。对象先在 Eden 区分配,经历 Minor GC 后存活对象进入 Survivor 区,年龄增长后晋升老年代。大对象直接进入老年代(由 -XX:PretenureSizeThreshold 控制)。
垃圾收集器
- Serial:单线程,STW 时间长。
- Parallel:多线程,JVM 默认,注重吞吐量。
- CMS:并发收集,STW 时间短,基于标记 - 清除,易碎片。
- G1:JDK9+ 默认,基于区域划分,可预测停顿时间,适合大内存。
类加载器
- BootstrapClassLoader:C++ 实现,加载核心类库。
- ExtensionClassLoader:加载扩展类库。
- AppClassLoader:加载 ClassPath 下的用户代码。
- 自定义 ClassLoader:继承 ClassLoader 实现特定逻辑。


