当前位置: 首页 > news >正文

Java 面试:线程池 7 个核心参数怎么理解?

摘要

线程池是 Java 面试里非常高频的并发题。很多人能背出corePoolSizemaximumPoolSizeworkQueue,但一问线程池执行流程、拒绝策略、为什么不建议使用Executors,就容易答乱。本文用面试视角,结合代码案例,梳理线程池的作用、7 个核心参数、任务执行流程、常见队列、拒绝策略以及实际开发中的使用建议。


前言

Java 后端面试里,线程池基本是绕不开的。

这个题看起来是八股,但实际开发中也经常用到,比如:

  • 异步处理;
  • 批量任务;
  • 定时任务;
  • 多线程导入;
  • 报表计算;
  • 接口并发调用;
  • 消息消费加速。

很多人背线程池,喜欢直接背 7 个参数。

但面试官真正想听的,不只是参数名,而是:

线程池为什么需要这些参数?任务提交进来后到底怎么执行?队列满了怎么办?线程数什么时候增加?什么时候拒绝?

这篇就用比较直接的方式,把线程池讲清楚。


一、面试官一般怎么问?

线程池常见问法有这些:

  • 线程池是干什么的?
  • 为什么要用线程池?
  • 线程池 7 个核心参数是什么?
  • 线程池任务执行流程是什么?
  • corePoolSizemaximumPoolSize有什么区别?
  • 常见拒绝策略有哪些?
  • 常见阻塞队列有哪些?
  • 为什么不建议使用Executors创建线程池?
  • 实际开发中线程池参数怎么配置?
  • 线程池使用不当会有什么问题?

二、线程池是干什么的?

一句话:

线程池是用来复用线程、控制并发数量、减少线程频繁创建和销毁成本的。

如果每来一个任务就创建一个线程:

new Thread(() -> { // 执行业务逻辑 }).start();

问题很明显:

  • 线程频繁创建和销毁,有性能损耗;
  • 线程数量不可控,容易把 CPU、内存打满;
  • 没有统一管理,不方便监控和关闭;
  • 任务多的时候,系统容易被拖垮。

线程池就是为了解决这些问题。

它可以把线程提前创建好,任务来了之后交给线程池执行。


三、线程池 7 个核心参数

自定义线程池一般使用ThreadPoolExecutor

构造方法如下:

public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler ) { // ... }

这 7 个参数分别是:

参数

含义

corePoolSize

核心线程数

maximumPoolSize

最大线程数

keepAliveTime

非核心线程空闲存活时间

unit

时间单位

workQueue

任务队列

threadFactory

线程工厂

handler

拒绝策略


四、corePoolSize:核心线程数

corePoolSize表示核心线程数。

简单理解:

线程池里长期保留的基础线程数量。

比如:

corePoolSize = 5

表示线程池会优先创建 5 个核心线程来处理任务。

只要任务不断进来,核心线程会一直负责执行。

默认情况下,核心线程即使空闲,也不会被销毁。

除非设置:

threadPoolExecutor.allowCoreThreadTimeOut(true);

五、maximumPoolSize:最大线程数

maximumPoolSize表示线程池允许创建的最大线程数。

比如:

corePoolSize = 5 maximumPoolSize = 10

意思是:

  • 平时先用 5 个核心线程处理任务;
  • 如果任务太多,队列也满了;
  • 线程池最多可以扩到 10 个线程。

需要注意:

不是任务一多,就马上创建到 maximumPoolSize。

线程池会先看核心线程,再看队列,最后才考虑创建非核心线程。

这个执行顺序很重要。


六、workQueue:任务队列

workQueue是任务队列。

当核心线程都忙时,新任务会先进入队列等待。

常见队列有:

1. ArrayBlockingQueue

有界阻塞队列,底层是数组。

new ArrayBlockingQueue<>(100);

特点:

  • 队列长度固定;
  • 满了之后不能继续放;
  • 更容易控制内存风险。

2. LinkedBlockingQueue

链表阻塞队列。

new LinkedBlockingQueue<>(100);

也可以不指定容量,但不建议。

如果不指定容量,默认容量非常大,容易堆积大量任务,导致内存风险。


3. SynchronousQueue

不存储任务的队列。

任务提交后必须马上有线程接收,否则就创建新线程或触发拒绝策略。

newCachedThreadPool里面就用了它。


4. PriorityBlockingQueue

优先级队列。

可以让任务按优先级执行。

但实际开发中用得相对少一些。


七、keepAliveTime 和 unit

keepAliveTime表示非核心线程空闲多久后会被回收。

比如:

keepAliveTime = 60 unit = TimeUnit.SECONDS

意思是:

非核心线程空闲超过 60 秒,就会被销毁。

注意,一般情况下,这个参数主要作用于非核心线程。

核心线程默认不会因为空闲而销毁。


八、threadFactory:线程工厂

threadFactory用来创建线程。

实际开发中建议自定义线程名称。

因为线上排查问题时,线程名非常重要。

例如:

ThreadFactory threadFactory = new ThreadFactory() { private final AtomicInteger index = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("order-task-pool-" + index.getAndIncrement()); return thread; } };

这样日志里能看到线程名:

order-task-pool-1 order-task-pool-2

如果线程池没有命名,线上排查会很难受。


九、handler:拒绝策略

当线程池已经达到最大线程数,并且任务队列也满了,新任务就会触发拒绝策略。

JDK 提供了 4 种常见拒绝策略。

1. AbortPolicy

默认策略,直接抛异常。

new ThreadPoolExecutor.AbortPolicy()

会抛:

RejectedExecutionException

2. CallerRunsPolicy

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

new ThreadPoolExecutor.CallerRunsPolicy()

这个策略不会直接丢任务。

但会让提交任务的线程变慢,相当于一种反压。


3. DiscardPolicy

直接丢弃任务,不抛异常。

new ThreadPoolExecutor.DiscardPolicy()

这个要慎用。

因为任务丢了,你可能还不知道。


4. DiscardOldestPolicy

丢弃队列中最老的任务,然后尝试提交当前任务。

new ThreadPoolExecutor.DiscardOldestPolicy()

这个也要慎用。

因为被丢弃的老任务可能也很重要。


十、线程池任务执行流程

这个是面试重点。

任务提交到线程池后,大概按这个顺序执行:

1. 如果当前线程数小于 corePoolSize,创建核心线程执行任务 2. 如果核心线程数已满,任务进入队列 3. 如果队列满了,并且当前线程数小于 maximumPoolSize,创建非核心线程执行任务 4. 如果线程数已经达到 maximumPoolSize,并且队列也满了,触发拒绝策略

一句话版本:

先核心线程,再任务队列,再最大线程,最后拒绝策略。

这个流程一定要记住。


十一、完整代码案例:自定义线程池

下面写一个完整示例。

import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class CustomThreadPoolDemo { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new NamedThreadFactory("biz-task-pool"), new ThreadPoolExecutor.CallerRunsPolicy() ); for (int i = 1; i <= 10; i++) { int taskNo = i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskNo); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); } static class NamedThreadFactory implements ThreadFactory { private final String prefix; private final AtomicInteger index = new AtomicInteger(1); public NamedThreadFactory(String prefix) { this.prefix = prefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName(prefix + "-" + index.getAndIncrement()); return thread; } } }

这个线程池配置是:

核心线程数:2 最大线程数:4 队列容量:3 拒绝策略:CallerRunsPolicy

任务执行过程大概是:

前 2 个任务创建核心线程执行 接下来 3 个任务进入队列 队列满了之后,再创建非核心线程 线程达到最大值后,再来的任务触发拒绝策略

十二、观察线程池执行流程

为了更直观看线程池状态,可以打印线程池信息。

import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class ThreadPoolStatusDemo { public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new NamedThreadFactory("status-pool"), new ThreadPoolExecutor.AbortPolicy() ); for (int i = 1; i <= 6; i++) { int taskNo = i; executor.execute(() -> { printStatus(executor, "执行任务前:" + taskNo); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } printStatus(executor, "执行任务后:" + taskNo); }); } TimeUnit.SECONDS.sleep(10); executor.shutdown(); } private static void printStatus(ThreadPoolExecutor executor, String message) { System.out.println("[" + message + "]" + " poolSize=" + executor.getPoolSize() + ", activeCount=" + executor.getActiveCount() + ", queueSize=" + executor.getQueue().size() + ", completedTaskCount=" + executor.getCompletedTaskCount()); } static class NamedThreadFactory implements ThreadFactory { private final String prefix; private final AtomicInteger index = new AtomicInteger(1); public NamedThreadFactory(String prefix) { this.prefix = prefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName(prefix + "-" + index.getAndIncrement()); return thread; } } }

这里可以看到几个指标:

方法

含义

getPoolSize()

当前线程池线程数

getActiveCount()

正在执行任务的线程数

getQueue().size()

当前队列中等待的任务数

getCompletedTaskCount()

已完成任务数

这些指标在排查线程池问题时很有用。


十三、拒绝策略代码案例

下面演示默认拒绝策略AbortPolicy

import java.util.concurrent.*; public class RejectPolicyDemo { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); for (int i = 1; i <= 3; i++) { int taskNo = i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskNo); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); } }

这个线程池配置是:

核心线程数:1 最大线程数:1 队列容量:1

提交 3 个任务时:

  • 第 1 个任务由线程执行;
  • 第 2 个任务进入队列;
  • 第 3 个任务无法执行,也无法入队;
  • 触发拒绝策略。

最终会抛出:

RejectedExecutionException

十四、为什么不建议使用 Executors?

JDK 提供了Executors工具类,可以快速创建线程池。

比如:

ExecutorService executor = Executors.newFixedThreadPool(10);

看起来很方便。

但实际开发中不建议直接使用。

原因是很多默认配置不够可控。


1. newFixedThreadPool

Executors.newFixedThreadPool(10);

底层使用的是无界队列:

LinkedBlockingQueue

如果任务提交速度远大于消费速度,队列会不断堆积,可能导致 OOM。


2. newSingleThreadExecutor

Executors.newSingleThreadExecutor();

它也是使用无界队列。

如果任务太多,同样可能堆积大量任务。


3. newCachedThreadPool

Executors.newCachedThreadPool();

它的最大线程数非常大。

如果任务非常多,可能创建大量线程,导致系统资源被打爆。


所以更推荐手动创建ThreadPoolExecutor

这样核心线程数、最大线程数、队列长度、线程名称、拒绝策略都可以自己控制。


十五、Executors 问题演示

下面是一个不推荐的例子。

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ExecutorsRiskDemo { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); for (int i = 1; i <= 100000; i++) { int taskNo = i; executor.execute(() -> { try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("执行任务:" + taskNo); }); } executor.shutdown(); } }

这个例子的问题是:

任务提交非常快 线程只有 2 个 任务执行很慢 队列会不断堆积

如果任务量非常大,就可能导致内存压力。

这就是为什么不建议无脑使用Executors.newFixedThreadPool()


十六、实际开发中线程池怎么配置?

线程池参数没有万能公式。

一般要根据任务类型来配置。

1. CPU 密集型任务

比如:

  • 复杂计算;
  • 加密解密;
  • 大量对象转换;
  • 本地计算。

这类任务主要消耗 CPU。

线程数不宜太多。

一般可以参考:

CPU 核心数 + 1

示例:

int cpuCount = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor executor = new ThreadPoolExecutor( cpuCount + 1, cpuCount + 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500), new NamedThreadFactory("cpu-task-pool"), new ThreadPoolExecutor.CallerRunsPolicy() );

2. IO 密集型任务

比如:

  • 查数据库;
  • 调远程接口;
  • 读写文件;
  • 网络请求。

这类任务经常在等待 IO 返回。

线程数可以比 CPU 核心数多一些。

比如:

CPU 核心数 * 2

或者根据接口耗时、QPS、机器资源压测后调整。

示例:

int cpuCount = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor executor = new ThreadPoolExecutor( cpuCount * 2, cpuCount * 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new NamedThreadFactory("io-task-pool"), new ThreadPoolExecutor.CallerRunsPolicy() );

注意:

这只是参考,不是绝对标准。

真实项目里还是要看任务耗时、数据量、数据库连接池、下游接口承载能力。


十七、实际案例:批量任务使用线程池

比如有一批用户数据,需要调用远程接口处理。

不要直接在循环里串行处理:

for (Long userId : userIds) { processUser(userId); }

可以用线程池并发处理。

import java.util.Arrays; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class BatchTaskDemo { public static void main(String[] args) throws InterruptedException { List<Long> userIds = Arrays.asList(1001L, 1002L, 1003L, 1004L, 1005L); ThreadPoolExecutor executor = new ThreadPoolExecutor( 3, 6, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new NamedThreadFactory("batch-user-pool"), new ThreadPoolExecutor.CallerRunsPolicy() ); CountDownLatch latch = new CountDownLatch(userIds.size()); for (Long userId : userIds) { executor.execute(() -> { try { processUser(userId); } finally { latch.countDown(); } }); } latch.await(); executor.shutdown(); System.out.println("批量任务处理完成"); } private static void processUser(Long userId) { System.out.println(Thread.currentThread().getName() + " 处理用户:" + userId); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } static class NamedThreadFactory implements ThreadFactory { private final String prefix; private final AtomicInteger index = new AtomicInteger(1); public NamedThreadFactory(String prefix) { this.prefix = prefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName(prefix + "-" + index.getAndIncrement()); return thread; } } }

这里用了:

  • 自定义线程池;
  • 自定义线程名称;
  • 有界队列;
  • CallerRunsPolicy
  • CountDownLatch等待所有任务完成。

这种写法在批量任务里比较常见。


十八、线程池使用注意事项

1. 不要无脑创建很多线程池

线程池不是越多越好。

一个系统里如果到处 new 线程池,很容易导致线程数不可控。

建议按业务类型统一管理。


2. 队列不要无界

无界队列容易堆积任务。

任务量一大,可能导致内存风险。

建议使用有界队列。


3. 线程要命名

线程名称很重要。

线上排查问题时,通过线程名可以快速定位是哪个业务线程池。


4. 拒绝策略要根据业务选择

如果任务不能丢,别用DiscardPolicy

如果允许调用线程自己执行,可以考虑CallerRunsPolicy

如果任务非常重要,要记录日志或落库补偿。


5. 记得关闭线程池

如果线程池不再使用,需要关闭。

executor.shutdown();

如果需要强制关闭:

executor.shutdownNow();

一般优先使用shutdown()


十九、面试回答模板

如果面试官问:

线程池 7 个参数是什么?执行流程是什么?

可以这样回答:

线程池主要用来复用线程、控制并发数量,避免频繁创建线程带来的性能损耗,也避免线程数量失控。 ThreadPoolExecutor 有 7 个核心参数: corePoolSize 是核心线程数; maximumPoolSize 是最大线程数; keepAliveTime 和 unit 表示非核心线程空闲多久后回收; workQueue 是任务队列; threadFactory 用来创建线程,通常会自定义线程名; handler 是拒绝策略。 线程池执行任务时,先看核心线程数是否已满。 如果核心线程没满,就创建核心线程执行任务; 如果核心线程满了,就把任务放入队列; 如果队列也满了,并且线程数还没到 maximumPoolSize,就创建非核心线程; 如果线程数已经达到 maximumPoolSize,队列也满了,就触发拒绝策略。 实际开发中不建议直接使用 Executors,因为很多默认队列是无界的,可能导致任务堆积和 OOM。更推荐手动创建 ThreadPoolExecutor,明确线程数、队列长度、线程名称和拒绝策略。

这段回答基本够用了。


二十、一句话总结

线程池不是只背 7 个参数。

更重要的是理解:

任务提交后,线程池到底怎么处理?

核心流程是:

核心线程 ↓ 任务队列 ↓ 非核心线程 ↓ 拒绝策略

面试时只要把下面几点讲清楚:

  • 线程池为什么存在;
  • 7 个参数分别干什么;
  • 任务执行流程;
  • 常见队列和拒绝策略;
  • 为什么不建议使用 Executors;
  • 实际开发怎么配置;

基本就不会乱。

大家加油:)


http://www.jsqmd.com/news/1151657/

相关文章:

  • YOLO26涨点改进| CVPR 2026 | 损失函数改进篇 | 引入 FoCIoU 更聚焦的损失函数,助力遥感目标检测、红外小目标检测、图像分类、图像分割任务,高效涨点
  • Python数据类型与进制及类型转换
  • 宠物领养平台
  • Biotinyl-GRF (1-43) (rat)
  • Vue3 defineEmits 与 v-model 双向绑定:3种事件模式实现表单组件
  • Facebook广告新素材为何跑不出消耗?CBO预算分配机制深度解析与ABO测试策略
  • 文字敏感词审核实现(本地实现)
  • J-Link RTT Viewer 与 MobaXterm 对比评测:2种方案连接速度与功能实测
  • Product Hunt 每日热榜 | 2026-07-08
  • 复杂网络中的最短路径搜索算法性能分析7
  • 张家口专业的GEO企业优化企业找哪家公司
  • 2026年AI智能体软件技术演进趋势与厂商能力深度对比评测排名
  • ChinaTest闭门会:AI原生质量工程,正在重构软件测试的下一站
  • 内核模块开发:module_init 与模块机制详解
  • Bloome多AI智能体协作平台:构建高效AI团队的技术实践
  • 03 module.json5 配置文件全解:能力声明、权限申请与设备支持
  • 从分布式视角看 SFT【直接提供考试答案给学生背下来,学生只能应对背过答案的考试试卷】、RL 以及OPD(On-Policy Distillation)【学生先自己做题,老师批改,学生修改提高能力】
  • Windows下关于flutter在cmd进行version或者doctor卡死的情况
  • 盲目扫描易踩红线,一文弄懂主动渗透和被动渗透
  • AI 赋能门诊场景:核心应用与实效展示
  • 平面变压器到底跑在什么频率上?
  • 基于STM32单片机智能温控风扇系统PWM调速 人体红外 蓝牙 成品31(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • Capvidia宣布发布2026 R1软件版本,以增强MBD准备就绪能力、QIF工作流以及数字主线自动化
  • OpenAI text-embedding-3-large 模型实战:构建10万条PDF文档向量知识库
  • 【计算机大数据毕业设计案例】基于 Django 的宁波五金电商线上销售营销可视化平台的设计与实现 基于时序数据统计的宁波五金电商营销热度分析系统(程序+文档+讲解+定制)
  • ChatGPT 学术写作 Prompt 工程:10个指令覆盖论文全流程,实测提升效率40%
  • Linux动态壁纸革命:告别静态桌面,拥抱沉浸式视觉体验
  • 2026 桌面AI实战!从零开发菜谱小程序:30分钟完成原本一天的工作量
  • dp 与 hdmi
  • 工业预诊:10 未来:全厂自愈系统