面试知识库

线程池 → Spring 异步 → MQ 消费者 追问链#

追问路径#

涉及知识点#

核心串联逻辑#

  1. 线程池本质:复用线程(避免创建销毁开销) + 控制并发度(防止资源耗尽) + 任务队列缓冲(削峰)
  2. 执行流程:提交任务 → 核心线程未满创建线程 → 满了放队列 → 队列满了创建到最大线程数 → 都满了执行拒绝策略
  3. Spring @Async陷阱:默认SimpleAsyncTaskExecutor不复用线程,高并发下OOM;必须配置ThreadPoolTaskExecutor
  4. MQ消费并发:Kafka的并发度=min(消费者数, partition数),扩消费者超过partition数无意义
  5. 动态调参:JDK ThreadPoolExecutor原生支持setCorePoolSize()热更新,配合配置中心实现不重启调整
  6. 代码示例
    // Spring自定义线程池 (必须配置,否则@Async用SimpleAsyncTaskExecutor!)
    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setRejectedExecutionHandler(new CallerRunsPolicy());
        return executor;
    }
    java

面试回答串联#

30秒速答#

“线程池7大参数,核心是corePoolSize、workQueue和拒绝策略。Spring @Async必须自定义Executor,默认的SimpleAsyncTaskExecutor不复用线程。MQ消费并发由partition数决定上限。线程池大小CPU密集型=核心数+1,IO密集型=核心数/(1-阻塞比例)。“

2分钟展开答#

“线程池的执行流程是:任务提交后先检查核心线程数,未满则创建核心线程;满了放入工作队列缓冲;队列也满了才创建到最大线程数;最大线程数也满了执行拒绝策略。Spring @Async有个大坑——默认使用SimpleAsyncTaskExecutor,每次调用都创建新线程不复用,高并发下约1 thread/ms直接OOM,必须自定义ThreadPoolTaskExecutor。拒绝策略选型上,关键业务推荐CallerRunsPolicy——调用者线程自己执行有反压效果,宁可慢不丢数据。线程池大小经验公式:CPU密集型设核心数+1(减少上下文切换),IO密集型用核心数/(1-阻塞比例)——4核CPU+80%时间IO阻塞则设20线程。实际阻塞比例通过Arthas trace命令或SkyWalking span分析确定。MQ消费者并发度受partition数限制,Kafka同一partition只能被组内一个消费者消费,扩消费者超过partition数无意义。积压时先扩partition+消费者,临时调大消费线程数快速消化。JDK原生支持setCorePoolSize()热更新线程池大小,配合Nacos可以不重启调整。“

相关追问链#