字节二面一个线程池问题,把我问懵了。

本引用仅用学习,禁用商用
引用自 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() 预热核心线程。

 

线程池的设计非常精妙,理解其背后的原理,才能更好地使用它。

希望这篇文章能帮你彻底搞懂线程池的初始化过程,下次面试再遇到这个问题,就可以自信地回答了!

 

posted @ 2025-05-25 22:21  IT6889  阅读(8)  评论(0)    收藏  举报