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

📅 2026/7/9 3:09:53
Java 面试:线程池 7 个核心参数怎么理解?
摘要线程池是 Java 面试里非常高频的并发题。很多人能背出corePoolSize、maximumPoolSize、workQueue但一问线程池执行流程、拒绝策略、为什么不建议使用Executors就容易答乱。本文用面试视角结合代码案例梳理线程池的作用、7 个核心参数、任务执行流程、常见队列、拒绝策略以及实际开发中的使用建议。前言Java 后端面试里线程池基本是绕不开的。这个题看起来是八股但实际开发中也经常用到比如异步处理批量任务定时任务多线程导入报表计算接口并发调用消息消费加速。很多人背线程池喜欢直接背 7 个参数。但面试官真正想听的不只是参数名而是线程池为什么需要这些参数任务提交进来后到底怎么执行队列满了怎么办线程数什么时候增加什么时候拒绝这篇就用比较直接的方式把线程池讲清楚。一、面试官一般怎么问线程池常见问法有这些线程池是干什么的为什么要用线程池线程池 7 个核心参数是什么线程池任务执行流程是什么corePoolSize和maximumPoolSize有什么区别常见拒绝策略有哪些常见阻塞队列有哪些为什么不建议使用Executors创建线程池实际开发中线程池参数怎么配置线程池使用不当会有什么问题二、线程池是干什么的一句话线程池是用来复用线程、控制并发数量、减少线程频繁创建和销毁成本的。如果每来一个任务就创建一个线程new Thread(() - { // 执行业务逻辑 }).start();问题很明显线程频繁创建和销毁有性能损耗线程数量不可控容易把 CPU、内存打满没有统一管理不方便监控和关闭任务多的时候系统容易被拖垮。线程池就是为了解决这些问题。它可以把线程提前创建好任务来了之后交给线程池执行。三、线程池 7 个核心参数自定义线程池一般使用ThreadPoolExecutor。构造方法如下public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable 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 和 unitkeepAliveTime表示非核心线程空闲多久后会被回收。比如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()会抛RejectedExecutionException2. 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十四、为什么不建议使用 ExecutorsJDK 提供了Executors工具类可以快速创建线程池。比如ExecutorService executor Executors.newFixedThreadPool(10);看起来很方便。但实际开发中不建议直接使用。原因是很多默认配置不够可控。1. newFixedThreadPoolExecutors.newFixedThreadPool(10);底层使用的是无界队列LinkedBlockingQueue如果任务提交速度远大于消费速度队列会不断堆积可能导致 OOM。2. newSingleThreadExecutorExecutors.newSingleThreadExecutor();它也是使用无界队列。如果任务太多同样可能堆积大量任务。3. newCachedThreadPoolExecutors.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 { ListLong 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; } } }这里用了自定义线程池自定义线程名称有界队列CallerRunsPolicyCountDownLatch等待所有任务完成。这种写法在批量任务里比较常见。十八、线程池使用注意事项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实际开发怎么配置基本就不会乱。大家加油