专业的JAVA编程教程与资源

网站首页 > java教程 正文

一天一道Java面试题,坚持三个月,菜鸟变大佬(线程池篇)

temp10 2025-07-01 23:06:10 java教程 3 ℃ 0 评论

一、基础概念篇

1. 为什么要用线程池?直接创建新线程有什么问题?
答:

  • 资源消耗:频繁创建/销毁线程消耗系统资源
  • 性能瓶颈:无限制创建线程会导致内存溢出(OOM)
  • 管理困难:缺乏统一管理可能导致线程泄露
  • 线程复用:池化技术提升响应速度(线程复用)

代码反例

一天一道Java面试题,坚持三个月,菜鸟变大佬(线程池篇)

// 错误示范:直接创建线程
new Thread(() -> {
    // 业务逻辑
}).start(); 

二、核心参数篇

2. 创建线程池的7个核心参数是什么?各有什么作用?
答:

参数名

作用说明

corePoolSize

核心线程数,即使空闲也不会被回收

maximumPoolSize

最大线程数

keepAliveTime

非核心线程空闲存活时间

unit

时间单位(TimeUnit)

workQueue

任务队列(阻塞队列实现)

threadFactory

线程工厂(定制线程属性)

rejectedExecutionHandler

拒绝策略(当队列和线程池满时的处理策略)

代码示例

// 标准创建方式
ExecutorService executor = new ThreadPoolExecutor(
    4, // core
    8, // max
    30, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
);

三、工作机制篇

3. 描述线程池处理任务的工作流程
答:

  1. 提交任务时,优先使用核心线程执行
  2. 核心线程全忙时,任务进入工作队列等待
  3. 队列满后,创建非核心线程处理任务
  4. 达到最大线程数后,触发拒绝策略

流程图

提交任务 → 核心线程是否满?  
  ↓是        ↓否  
任务入队 → 队列是否满?  
  ↓是        ↓否  
创建非核心线程 → 是否超过maxSize?  
               ↓是  
           执行拒绝策略

四、拒绝策略篇

4. JDK提供的4种拒绝策略及其使用场景
答:

策略类

行为描述

适用场景

AbortPolicy

直接抛出
RejectedExecutionException

需要严格保证数据完整性

CallerRunsPolicy

由提交任务的线程执行该任务

需要减缓任务提交速度

DiscardPolicy

静默丢弃任务

允许丢失任务的场景

DiscardOldestPolicy

丢弃队列最旧任务并重试提交

允许丢弃旧任务的实时系统

企业级实践

// 自定义拒绝策略(记录日志+监控)
new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        log.warn("Task {} rejected, queueSize={}", r, executor.getQueue().size());
        // 上报监控系统
        Metrics.counter("thread_pool_rejected").increment();
    }
}

五、配置优化篇

5. 如何合理配置线程池大小?
答: 根据任务类型采用不同策略:

  • CPU密集型
// 公式:线程数 = CPU核心数 + 1
int coreSize = Runtime.getRuntime().availableProcessors() + 1;
  • IO密集型
// 公式:线程数 = CPU核心数 * (1 + 平均等待时间/平均计算时间)
// 经验值:一般设置为CPU核心数 * 2~5
int coreSize = Runtime.getRuntime().availableProcessors() * 3; 

企业级配置示例

// 电商订单处理线程池(IO密集型)
EnterpriseThreadPool.newBuilder("OrderProcess")
    .corePoolSize(16)
    .maxPoolSize(32)
    .queueCapacity(1000)
    .keepAliveTime(120)
    .build();

六、高级特性篇

6. 如何实现线程池的优雅关闭?
答:

// 分阶段关闭策略
executor.shutdown(); // 停止接收新任务
try {
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        executor.shutdownNow(); // 强制终止
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}

注意事项

  • 必须处理未完成的任务(如持久化队列任务)
  • 使用shutdownNow()会返回未执行的任务列表
  • 结合Runtime.addShutdownHook()实现JVM关闭钩子

七、监控与调优篇

7. 如何实现线程池的运行时监控?
答: 通过ThreadPoolExecutor的API获取关键指标:

// 监控指标示例
int activeCount = executor.getActiveCount(); // 活跃线程数
long completedCount = executor.getCompletedTaskCount(); // 已完成任务
int queueSize = executor.getQueue().size(); // 队列积压量

企业级监控方案

  1. 定时采集指标(如每5秒)
  2. 集成Prometheus/Grafana可视化
  3. 设置报警阈值(如队列使用率>80%)

八、高频陷阱题

8. 为什么禁止使用Executors创建线程池?
答:

  • newFixedThreadPool:使用无界队列(LinkedBlockingQueue),可能引起OOM
  • newCachedThreadPool:最大线程数为Integer.MAX_VALUE,可能创建过多线程
  • newSingleThreadExecutor:同样存在无界队列问题

正确做法

// 必须显式指定队列容量
new ThreadPoolExecutor(..., new LinkedBlockingQueue<>(1000), ...);

九、终极拷问

9. 如果核心线程数设置为0会怎样?
答:

  • 提交第一个任务时,会直接进入队列(违反直觉)
  • 需要等队列满后才会创建非核心线程
  • 正确做法:核心线程数至少设置为1

源码解析

// ThreadPoolExecutor.execute()部分逻辑
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true)) return; // 核心线程处理
}
if (workQueue.offer(command)) { ... } // 入队列

十、扩展思考题

10. 如何实现动态线程池参数调整?
答:

  1. 使用setCorePoolSize()和setMaximumPoolSize()方法
  2. 配合配置中心实现热更新(如Nacos/Apollo)
  3. 队列容量调整需自定义队列实现(JDK队列不支持动态扩容)

代码示例

// 动态调整核心线程数
executor.setCorePoolSize(newCoreSize);
// 动态调整最大线程数
executor.setMaximumPoolSize(newMaxSize);

总结建议

掌握线程池原理需结合:

  1. 阅读ThreadPoolExecutor源码(重点关注execute()方法)
  2. 使用Arthas等工具进行运行时分析
  3. 在生产环境配置合理的监控告警
  4. 参考阿里《Java开发手册》线程池规约

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表