字节二面一个线程池问题,把我问懵了。
本引用仅用学习,禁用商用
引用自 https://mp.weixin.qq.com/s/fJsYrkBIN6QEK_Xwzsz0eg
之前收到读者面试字节时,被问到一个关于线程池的问题。
1. 先看一段代码
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
System.out.println("当前线程数:" + executor.getPoolSize());
}
问题来了: 这段代码执行后,线程池里到底有没有线程?
如果对 线程池比较熟悉的同学,应该一眼就能看出打印0。但是具体为什么,下面我们具体分析下。
2. 上代码。
为了找到答案,我们直接翻看 ThreadPoolExecutor 的源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
Executors.defaultThreadFactory(),
defaultHandler);
}
重点来了: 在构造函数中,并没有直接创建线程!
2.1 线程到底啥时候创建?
线程的创建时机,其实是在提交任务的时候!我们继续看源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果当前线程数小于核心线程数,创建新线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果线程池正在运行且任务成功入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果任务入队失败,尝试创建新线程
else if (!addWorker(command, false))
reject(command); // 触发拒绝策略
}
addWorker(command, true):尝试创建一个核心线程。workQueue.offer(command):尝试将任务放入任务队列。addWorker(command, false):尝试创建一个非核心线程。
关键点:当提交任务时,如果当前线程数小于核心线程数,就会调用 addWorker 方法创建新的线程来执行任务。
2.2 线程创建的核心方法:addWorker()
private boolean addWorker(Runnable firstTask, boolean core) {// 省略部分代码...Worker w = new Worker(firstTask);Thread t = w.thread;if (t != null) {workers.add(w);t.start(); // 启动线程}return true;}
Worker:线程池中的工作线程,封装了 Thread 和任务。t.start():启动线程,开始执行任务。
回到最初的问题:线程池创建后,里面是没有线程的! 只有在提交任务时,才会根据需要创建线程。
3. 通过 Demo 验证
我们可以通过一个简单的实验来验证线程池的线程创建时机:
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10) );
System.out.println("线程池创建完成,当前线程数:" + executor.getPoolSize());
executor.execute(() -> System.out.println("任务 1 执行,线程名:" + Thread.currentThread().getName()));
System.out.println("任务 1 提交后,当前线程数:" + executor.getPoolSize());
executor.execute(() -> System.out.println("任务 2 执行,线程名:" + Thread.currentThread().getName()));
System.out.println("任务 2 提交后,当前线程数:" + executor.getPoolSize());
executor.shutdown();
}
打印结果:
线程池创建完成,当前线程数:0 任务1 提交后,当前线程数:1 任务1 执行,线程名:pool-1-thread-1 任务2 提交后,当前线程数:2 任务2 执行,线程名:pool-1-thread-2
从输出结果可以看出,线程池在创建时并没有立即创建线程,而是在任务提交时才按需创建。
4. 如何让线程池创建后就有线程?
如果面试官进一步追问:“那有没有办法让线程池创建后就有线程呢?”
方法 1:预热一个核心线程。
prestartCoreThread()方法会立即创建 1 个核心线程,即使没有任务。
executor.prestartCoreThread();System.out.println("当前线程数:" + executor.getPoolSize()); // 输出 1
方法 2:预热所有核心线程。
-
prestartAllCoreThreads()会立即创建 corePoolSize 个线程,适用于高并发任务场景。
executor.prestartAllCoreThreads();System.out.println("当前线程数:" + executor.getPoolSize()); // 输出 2
5. 总结
问:线程池创建后,里面到底有没有线程?
- 线程池创建时,内部并没有线程。线程是按需创建的,只有在任务提交时才会根据核心线程数、任务队列和最大线程数的配置来创建线程。
问:如何让线程池创建后就有线程?
- 如果希望线程池创建后就有线程,可以使用
prestartCoreThread()或prestartAllCoreThreads()预热核心线程。
线程池的设计非常精妙,理解其背后的原理,才能更好地使用它。
希望这篇文章能帮你彻底搞懂线程池的初始化过程,下次面试再遇到这个问题,就可以自信地回答了!

浙公网安备 33010602011771号