面试官:Java线程池的核心参数了解吗?谢飞机:会用new Thread就不错了!

43 阅读4分钟

面试官:Java线程池的核心参数了解吗?谢飞机:会用new Thread就不错了!

面试官:请进。

谢飞机:您好,我是来面试Java开发岗的谢飞机。

面试官:嗯,坐吧。我看你简历上写了熟悉多线程编程,那我们先聊聊线程池。

第一轮:基础概念与使用

面试官:你说说,Executors.newFixedThreadPool(5) 创建的是什么样的线程池?

谢飞机:哦这个我知道!就是固定大小为5的线程池,最多同时运行5个线程,其他的任务会排队等着。

面试官:不错。那如果我想自定义一个线程池,应该用哪个类?

谢飞机ThreadPoolExecutor!构造函数里可以设置核心线程数、最大线程数、空闲时间、队列……

面试官:很好。那你说说这几个参数分别代表什么?

谢飞机:corePoolSize是核心线程数,keepAliveTime是非核心线程空闲存活时间,maximumPoolSize是最大线程数,workQueue是任务队列……

面试官:回答得很清楚,基本功不错。

第二轮:深入原理与问题排查

面试官:假设你现在有一个高并发系统,突然发现线程池拒绝了很多任务,可能是什么原因?

谢飞机:啊……可能是任务太多了吧?队列满了?

面试官:对,有可能是队列满了。那你用过哪种拒绝策略?

谢飞机:默认的好像是直接抛异常?我还听说过有让调用者自己执行的……

面试官:叫什么名字?

谢飞机:呃……Caller什么来着……CallerRunsPolicy?

面试官:勉强答对。那你说说 submit()execute() 有什么区别?

谢飞机:嗯……submit 可以返回结果?就是 Future?execute 就是直接扔进去不管了?

面试官:还行。那你知道线程池是如何复用线程的吗?

谢飞机:这个……应该是线程执行完一个任务后不销毁,继续从队列里取下一个任务执行?

面试官:没错,看来你还知道一点。

第三轮:扩展与其他技术关联

面试官:你在项目中用过 Spring 的 @Async 吗?

谢飞机:用过用过!加个注解就能异步执行,贼方便!

面试官:那你知道它底层用的是什么线程池吗?

谢飞机:JDK 自带的那个?好像是 SimpleAsyncTaskExecutor?不对不对,那个好像不是真的线程池……

面试官:那你有没有配置过自定义线程池给 @Async 使用?

谢飞机:呃……没有耶,都是默认的。

面试官:RabbitMQ 消费消息的时候,怎么控制并发消费?

谢飞机:这个……是不是有个 concurrency 参数?可以设成5,就有5个消费者线程?

面试官:那这个底层是不是也用了线程池?

谢飞机:应该是吧……我也没看过源码……

面试官:好了,今天就到这里。你的基础还可以,但深度不够。回去等通知吧。

谢飞机:好的好的,谢谢面试官!


答案详解

1. 线程池核心参数

ThreadPoolExecutor 构造函数有7个参数:

  • corePoolSize:核心线程数,即使空闲也不会被回收(除非设置 allowCoreThreadTimeOut)
  • maximumPoolSize:最大线程数
  • keepAliveTime:非核心线程空闲存活时间
  • unit:存活时间单位
  • workQueue:阻塞队列,如 LinkedBlockingQueueArrayBlockingQueue
  • threadFactory:线程工厂,用于创建线程
  • handler:拒绝策略

2. 拒绝策略

四种内置拒绝策略:

  • AbortPolicy:直接抛出 RejectedExecutionException(默认)
  • CallerRunsPolicy:由提交任务的线程来执行该任务
  • DiscardPolicy:静默丢弃任务,不处理
  • DiscardOldestPolicy:丢弃队列中最老的任务,然后重试执行当前任务

3. submit() vs execute()

  • execute(Runnable command):来自 Executor 接口,无返回值,出错直接抛异常
  • submit() 方法来自 ExecutorService,有三个重载:
    • submit(Runnable task):返回 Future<?>,结果为 null
    • submit(Runnable task, T result):返回 Future<T>,结果为传入的 result
    • submit(Callable<T> task):返回 Future<T>,可获取返回值

submit() 内部将任务包装成 FutureTask,异常不会立即抛出,而是存储在 Future 中,调用 get() 时才会抛出 ExecutionException

4. Spring @Async 底层实现

Spring 默认使用的线程池是 SimpleAsyncTaskExecutor,但它不是真正的线程池,每个任务都会创建新线程!生产环境必须自定义线程池:

@Configuration
@EnableAsync
public class AsyncConfig {

@Bean(&quot;taskExecutor&quot;)
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix(&quot;async-pool-&quot;);
    executor.setRejectedExecutionHandler(new ThreadPoolTaskExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}




}




// 使用
@Async("taskExecutor")
public void asyncMethod() { ... }

// 使用 @Async("taskExecutor") public void asyncMethod() { ... }

5. RabbitMQ 并发消费

通过 SimpleMessageListenerContainer 配置并发:

@Bean
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames("my.queue");
    container.setConcurrentConsumers(5);  // 并发消费者数量
    container.setMaxConcurrentConsumers(10); // 最大并发
    return container;
}

其底层使用 Spring 的任务执行框架,最终也是交给线程池去调度执行消费者线程。