面试官: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:阻塞队列,如
LinkedBlockingQueue、ArrayBlockingQueue - threadFactory:线程工厂,用于创建线程
- handler:拒绝策略
2. 拒绝策略
四种内置拒绝策略:
- AbortPolicy:直接抛出 RejectedExecutionException(默认)
- CallerRunsPolicy:由提交任务的线程来执行该任务
- DiscardPolicy:静默丢弃任务,不处理
- DiscardOldestPolicy:丢弃队列中最老的任务,然后重试执行当前任务
3. submit() vs execute()
execute(Runnable command):来自Executor接口,无返回值,出错直接抛异常submit()方法来自ExecutorService,有三个重载:submit(Runnable task):返回Future<?>,结果为 nullsubmit(Runnable task, T result):返回Future<T>,结果为传入的 resultsubmit(Callable<T> task):返回Future<T>,可获取返回值
submit() 内部将任务包装成 FutureTask,异常不会立即抛出,而是存储在 Future 中,调用 get() 时才会抛出 ExecutionException。
4. Spring @Async 底层实现
Spring 默认使用的线程池是 SimpleAsyncTaskExecutor,但它不是真正的线程池,每个任务都会创建新线程!生产环境必须自定义线程池:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-pool-");
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 的任务执行框架,最终也是交给线程池去调度执行消费者线程。