线程池常见面试题

6 篇文章 0 订阅
4 篇文章 0 订阅

一、请说出线程池的工作原理

答:常见流程如下:

  • 提交任务后,先判断当前池中线程数是否小于 corePoolSize,如果小于,则创建新线程执行这个任务。
  • 否则,判断线程池任务队列是否已满,如果没有满,则添加任务到任务队列。
  • 否则,判断当前池中线程数是否大于 maximumPoolSize,如果大于则执行预设拒绝策略。
  • 否则,创建一个线程执行该任务,直至线程数达到maximumPoolSize,达到后执行预设拒绝策略。

        注意:其实按照上面流程回答已经差不多了,但是jdk1.6之后对流程做了一点点优化,在corePoolSize=0的时候任务不会长期阻塞。

        jdk1.6之前:假设线程池当前corePoolSize=0时,则会判断线程池任务队列容量是否已满,若未满,则将任务添加进任务队列进行排队,并不会创建新的线程。

        jdk1.6之后:假设线程池当前corePoolSize=0时,任务提交成功后会创建一个firstTask 为 null 的 worker,这个 worker 会从等待队列中获取任务并执行。

 /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        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);
    }

二、线程池创建之后,会立即创建核心线程么

        答:查看线程池的构造器可以发现并不会立即创建核心线程,而是等到有任务提交时才会开始创建线程,除非调用了prestartCoreThread/prestartAllCoreThreads 事先启动核心线程。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

prestartCoreThread: 启动一个核心线程,使其空闲等待工作。这会覆盖仅在执行新任务时启动核心线程的默认策略。如果所有核心线程都已启动,此方法将返回 false。如果线程已启动,则返回 true。

    public boolean prestartCoreThread() {
        return workerCountOf(ctl.get()) < corePoolSize &&
            addWorker(null, true);
    }

 prestartAllCoreThreads:启动所有核心线程,使它们空闲等待工作。这会覆盖仅在执行新任务时启动核心线程的默认策略。返回启动的线程数。

    public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }

三、核心线程永远不会销毁么

        答:其实核心线程只是一个动态概念,在jdk中并没有给线程打上"core"标记。而在jdk1.6之前,线程池会尽量保证会有corePoolSize个线程存活,即使这些线程已经闲置了很长的时间,这样会造成一部分资源浪费;于是在1.6开始,jdk提供了一个allowCoreThreadTimeOut方法用于控制核心线程是否被销毁:

    /**
     *设置控制核心线程是否可以超时并在保持活动时间内没有任务到达时终止的策略,
     *如果需要,当新任务到达时被替换。当为 false 时,核心线程永远不会由于缺少传入任务而终止。
     *如果为true,则适用于非核心线程的存活策略也适用于核心线程。
     *为避免持续的线程替换,设置 {@code true} 时的 keep-alive 时间必须大于零。
     *且通常应该在主动使用池之前调用此方法。
     *
     * @param value {@code true} if should time out, else {@code false}
     * @throws IllegalArgumentException if value is {@code true}
     *         and the current keep-alive time is not greater than zero
     *
     * @since 1.6
     */    
    public void allowCoreThreadTimeOut(boolean value) {
        if (value && keepAliveTime <= 0)
            throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
        if (value != allowCoreThreadTimeOut) {
            allowCoreThreadTimeOut = value;
            if (value)
                interruptIdleWorkers();
        }
    }

注意: 这种策略和corePoolSize=0是有区别的

  • corePoolSize=0:在一般情况下只使用一个线程消费任务,只有当并发请求特别多、等待队列都满了之后,才开始用多线程。
  • allowsCoreThreadTimeOut=true && corePoolSize>1:在一般情况下就开始使用多线程(corePoolSize 个),当并发请求特别多,等待队列都满了之后,继续加大线程数。但是当请求没有的时候,允许核心线程也终止。

        综合来看,其实corePoolSize=0的效果基本等同于allowsCoreThreadTimeOut=true&&corePoolSize=1,只是实现细节不同。

四、线程池参数keepAliveTime=0 会怎么样

        答:keepAliveTime这个参数1.6之前控制的是非核心线程的存活时间,且该参数值不能小于0,否则在创建线程池时会抛出异常。而设置为0的含义其实是指非核心线程执行完属于自己的任务后即刻销毁。从1.6开始,若 allowsCoreThreadTimeOut=true,则keepAliveTime必须大于0,否则也会报错。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答:Java线程池是Java中的一个重点知识,并且在Java的工作中经常会遇到,因此在面试中也是必问的面试目。以下是一些常见的Java线程池面试: 1. 谈谈什么是线程池? 2. 为什么要使用线程池? 3. 你们哪些地方会使用到线程池? 4. 线程池有哪些作用? 5. 线程池的创建方式有哪些? 6. 线程池底层是如何实现复用的? 7. ThreadPoolExecutor核心参数有哪些? 8. 线程池创建的线程会一直在运行状态吗? 9. 为什么阿里巴巴不建议使用Executors? 10. 线程池的底层实现原理是什么? 11. 线程池队列满了,任务会丢失吗? 12. 线程池的拒绝策略类型有哪些? 13. 线程池如何合理配置参数? 这些问涵盖了线程池的基本概念、使用场景、实现原理以及相关的配置和策略等方面的知识。了解这些问能够帮助面试者更好地理解和应用Java线程池。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [java线程池面试有哪些?java线程池常见面试](https://blog.csdn.net/muli525/article/details/123553744)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [(一)【Java精选面试线程池底层实现原理(含答案)](https://blog.csdn.net/qq_30999361/article/details/124924343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值