ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java 线程池 ThreadPoolExecutor 笔记

2026/10/7 2:40:45 拓冰建站 浏览量
Java 线程池 ThreadPoolExecutor 笔记 Java线程池ThreadPoolExecutor超全面试干货笔记线程池是Java后端面试必考核心知识点也是实际开发中高频使用的工具。很多开发者只会简单使用Executors工具类却不懂底层原理、参数配置、执行流程和避坑规则。本文整合线程池全套核心知识包含ThreadPoolExecutor核心参数、执行流程、线程工厂、核心线程数配置、手写线程池、变量捕获考点全程通俗易懂适配面试背诵与项目实战。一、线程池基础认知日常开发中常用的Executors.newFixedThreadPool()等工厂方法底层全部基于ThreadPoolExecutor封装实现。核心区别ThreadPoolExecutor线程池原始核心类功能最全、自由度最高可自定义所有参数生产环境推荐直接使用Executors工厂方法仅为快捷创建工具提前封装好参数屏蔽底层细节存在潜在风险不推荐生产直接使用线程池的核心工作只有两件事创建线程池构造参数、提交任务执行。基础使用示例ExecutorService service Executors.newFixedThreadPool(4); // 提交无返回值任务 service.submit(new Runnable() { Override public void run() { System.out.println(hello); } });任务提交分为两种submit()、execute()后文会详细区分两者差异。二、ThreadPoolExecutor 七大核心构造参数面试必考ThreadPoolExecutor 最全构造方法包含7个参数其余重载构造方法只是省略部分参数、使用默认值面试核心考察完整版7参数原理。public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲存活时间 TimeUnit unit, // 时间单位 BlockingQueueRunnable workQueue, // 阻塞任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )1. corePoolSize 核心线程数线程池中常驻不回收的线程数量是线程池的基础工作线程。只要线程池不关闭核心线程默认会一直存在不会因空闲被销毁。2. maximumPoolSize 最大线程数线程池允许创建的线程总数上限。线程池线程数量动态范围corePoolSize ≤ 当前线程数 ≤ maximumPoolSize。通俗比喻核心线程是正式员工永久在岗最大线程数是全员上限超出核心线程的线程为临时工可动态销毁。3. keepAliveTime TimeUnit unit 空闲存活时间两个参数配合使用控制**非核心线程临时工**的空闲销毁规则keepAliveTime非核心线程的最大空闲时长unit时间单位毫秒、秒、分钟等通俗理解临时工长时间摸鱼无任务超过指定时长就会被辞退销毁核心线程不受该参数控制。4. BlockingQueue workQueue 阻塞任务队列用于存储等待执行的任务当核心线程全部繁忙时新提交的任务会进入该队列排队是线程池的缓冲容器。常用队列类型ArrayBlockingQueue有界队列固定容量任务满后会触发后续流程LinkedBlockingQueue可无界/有界适合任务量波动大的场景无界队列可能导致OOMPriorityBlockingQueue优先级队列可根据任务优先级排序执行5. ThreadFactory threadFactory 线程工厂专门用于统一创建线程的工厂接口可批量设置线程名称、优先级、守护线程等属性后文单独精讲核心考点。6. RejectedExecutionHandler handler 拒绝策略触发时机核心线程已满 阻塞队列已满 线程数达到最大值此时新任务会触发拒绝策略。JDK内置4种高频面试策略拒绝策略核心作用通俗理解AbortPolicy默认直接抛出RejectedExecutionException异常终止任务任务过载直接报错崩溃CallerRunsPolicy由提交任务的线程自行执行该任务不新开线程谁提交任务谁自己干活DiscardOldestPolicy丢弃队列中排队最久的旧任务执行新任务淘汰旧任务给新任务让位DiscardPolicy静默丢弃新任务无任何异常抛出直接忽略新任务无报错无日志三、线程池完整执行流程必背核心所有任务提交后严格遵循以下流程执行一环扣一环新任务提交判断当前线程数是否小于核心线程数是则直接新建核心线程执行任务核心线程已满判断阻塞队列是否未满是则将任务放入队列排队队列已满判断当前线程数是否小于最大线程数是则新建非核心线程执行任务线程数已达到最大值触发拒绝策略任务执行完毕后非核心线程空闲超过 keepAliveTime自动销毁仅保留核心线程常驻一句话速记核心线程常驻队列排队满了开临时工达到最大上限执行拒绝策略临时工空闲超时销毁。四、高频补充面试考点1. 禁止直接使用Executors工厂方法根据阿里开发手册规范生产环境不推荐Executors快捷创建线程池部分工厂方法底层使用无界队列任务大量堆积时会耗尽内存导致OOM内存溢出。推荐手动new ThreadPoolExecutor自定义7项参数可控性更强。2. execute与submit方法区别execute()仅提交无返回值任务无法获取任务执行结果submit()可提交任务返回Future对象支持获取任务执行结果、捕获异常3. 线程回收规则核心线程默认永久常驻不会因空闲超时回收仅非核心线程受空闲时间控制超时自动销毁。五、ThreadFactory线程工厂 精讲面试重点1. 核心定义ThreadFactory 是专门用于统一创建线程的接口线程池所有线程均由该工厂生成而非直接new Thread()。public interface ThreadFactory { Thread newThread(Runnable r); }2. 为什么必须用线程工厂如果直接new Thread创建线程存在大量痛点默认线程名Thread-0、Thread-1线上报错无法定位业务场景线程优先级、守护线程属性无法统一管控批量创建线程代码冗余、混乱不可控核心价值统一批量创建线程、规范线程属性、简化代码、方便线上日志排查问题实现线程创建与业务逻辑解耦。3. JDK默认线程工厂缺陷默认工厂生成的线程名为 pool-1-thread-1、pool-1-thread-2命名笼统无业务标识线上故障难以快速定位问题生产环境必须自定义线程工厂。4. 自定义线程工厂可直接落地企业开发核心目的自定义业务线程名、统一线程属性、统一异常处理。// 自定义线程工厂极简示例 ThreadFactory factory r - { Thread t new Thread(r); t.setName(my-biz-thread- t.getId()); // 自定义业务线程名方便排查 t.setDaemon(false); // 设置为用户线程 return t; }; // 自定义线程池时传入工厂 new ThreadPoolExecutor( 5, 10, 10, TimeUnit.SECONDS, new(100), factory );5. 终极背诵总结线程工厂是线程池的线程生成器负责统一创建线程、规范线程名称和属性解决默认线程无法排查问题的痛点是企业规范开发的必填参数。六、线程池核心线程数最优配置面试高频网上流传的N、N1、2N等固定公式并非标准答案线程数不能写死核心依据是任务类型最终需通过压测调优N为CPU逻辑核心数。1. CPU密集型任务任务以算术运算、逻辑判断、循环计算为主全程占用CPU资源。原理单个CPU核心同一时刻只能执行1个计算任务线程过多会触发频繁的线程上下文切换增加系统开销降低性能配置建议核心线程数 ≤ CPU逻辑核心数N举例4核机器CPU密集任务最优核心线程数为42. IO密集型任务任务大部分时间处于阻塞等待状态数据库查询、网络请求、文件读写不占用CPU。原理线程IO阻塞时CPU空闲可调度其他线程执行单个CPU核心可轮询处理多个线程配置建议线程数可大于N根据阻塞时长调整初始估算公式N * (1 等待时间 / CPU计算时间)3. 工程落地标准流程根据任务类型给出初始线程数通过压测不断调整参数观测QPS、CPU使用率、任务平均响应时间筛选出响应快、CPU压力合理的最优配置4. 通俗比喻CPU核心厨师CPU密集是厨师全程炒菜人多会互相抢资源、切换耗时IO密集是厨师大部分时间等水开、等食材空闲时可处理更多任务可多安排线程。5. 面试简答模板线程池数量不能固定配置需区分任务类型CPU密集型任务线程数不超过CPU核心数避免上下文切换开销IO密集型任务线程数可大于核心数利用CPU空闲资源真实业务多为混合任务公式仅作初始参考最终需通过压测确定最优值。七、手写简易线程池 变量捕获考点难点突破1. 手写简易线程池实现通过阻塞队列常驻线程实现线程池核心原理看懂即吃透线程池底层逻辑。class MyThreadPool { // 阻塞队列存放待执行任务 private BlockingQueueRunnable queue new(100); // 提交任务队列满则阻塞等待 public void submit(Runnable runnable) throws InterruptedException { queue.put(runnable); } // 初始化线程池创建n个常驻工作线程 public MyThreadPool(int n) { for (int i 0; i n; i) { Thread t new Thread(() - { try { // 队列空则阻塞等待任务 Runnable runnable queue.take(); runnable.run(); } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); } } }2. 匿名内部类变量捕获报错问题循环中直接捕获for循环变量i会编译报错for (int i 1000; i) { myThreadPool.submit(new Runnable() { Override public void run() { System.out.println(执行任务 i); // 编译报错 } }); }核心规则匿名内部类、Lambda表达式捕获局部变量时变量必须是final或事实final赋值后不再修改。for循环中的i会持续自增属于可变变量匿名内部类拷贝的变量值会和原值不一致存在线程安全问题因此编译器禁止捕获。3. 标准解决方案循环内定义临时变量满足事实final特性for (int i 0; i 1000; i) { int id i; // 单次循环新建变量赋值后永不修改 myThreadPool.submit(new Runnable() { Override public void run() { System.out.println(执行任务 id); // 正常编译执行 } }); }原理每轮循环都会新建独立的id变量赋值后不再修改满足事实final要求可被匿名内部类正常捕获。4. 避坑提醒不推荐将i提升为成员变量会扩大变量作用域且多线程并发读写会导致数据错乱、重复存在严重线程安全问题代码不优雅、不安全。5. 一句话背诵匿名内部类捕获局部变量必须不可变for循环i持续变化无法直接捕获循环内新建临时变量满足事实final即可解决问题。八、总结底层核心类与工厂方法区别、7大参数原理、4种拒绝策略、完整执行流程、线程工厂作用与自定义、线程数配置规则、手写线程池原理、变量捕获机制。所有知识点贴合面试提问场景可直接背诵用于面试应答同时适配生产环境开发规范。