面试官问线程池哪些参数是可以在运行阶段修改的?

一、首先一张图先回忆下java线程池的工作原理

 二、线程池的核心参数都有哪些?

直接看JDK源码:

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

看作者这个英文注释就能知道:

参数 说明
corePoolSize 核心线程数大小:不管它们创建以后是不是空闲的。线程池需要保持 corePoolSize 数量的线程,除非设置了 allowCoreThreadTimeOut
maximumPoolSize 最大线程数:线程池中最多允许创建 maximumPoolSize 个线程
keepAliveTime 存活时间:如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,那就回收。
unit keepAliveTime 的时间单位。
workQueue 存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在这里。它仅仅用来存放被 execute 方法提交的 Runnable 任务。
threadFactory 线程工程:用来创建线程工厂。比如这里面可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。
handler 拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略。

三、先来看一个线程池参数修改的代码示例

package test.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author leijs
 * @date 2021/8/17
 */
public class ThreadPoolTest {

    public static void main(String[] args) throws Exception{
        int cpuSize = Runtime.getRuntime().availableProcessors();
        // 创建线程池
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                1,
                cpuSize * 4,
                30,
                /*可以指定外部ThreadFactory*/
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(200),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        // 打印修改之前参数
        System.out.println("修改前:");
        System.out.println("核心线程数:" + poolExecutor.getCorePoolSize());
        System.out.println("最大线程数:" +poolExecutor.getMaximumPoolSize());
        System.out.println("线程池大小:" +poolExecutor.getPoolSize());
        System.out.println("达到过的最大值:" +poolExecutor.getLargestPoolSize());
        System.out.println("正在使用的线程数:" +poolExecutor.getActiveCount());
        System.out.println("任务数:" +poolExecutor.getTaskCount());
        System.out.println("完成的任务数:" + poolExecutor.getCompletedTaskCount());
        System.out.println("阻塞队列:" +poolExecutor.getQueue());
        System.out.println("拒绝策略:" +poolExecutor.getRejectedExecutionHandler());
        System.out.println("线程工厂:" +poolExecutor.getThreadFactory());
        poolExecutor.execute(() -> {
            // 执行修改
            poolExecutor.setCorePoolSize(10);
            poolExecutor.setMaximumPoolSize(cpuSize * 5);
            poolExecutor.setKeepAliveTime(60, TimeUnit.SECONDS);
            poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        });

        // 等一会不然打印看不出效果
        Thread.sleep(10000);
        // 打印修改结果
        System.out.println("修改后:");
        System.out.println("核心线程数:" + poolExecutor.getCorePoolSize());
        System.out.println("达到过的最大值:" +poolExecutor.getMaximumPoolSize());
        System.out.println("线程池大小:" +poolExecutor.getPoolSize());
        System.out.println("最大线程数:" +poolExecutor.getLargestPoolSize());
        System.out.println("正在使用的线程数:" +poolExecutor.getActiveCount());
        System.out.println("任务数:" +poolExecutor.getTaskCount());
        System.out.println("完成的任务数:" + poolExecutor.getCompletedTaskCount());
        System.out.println("阻塞队列:" +poolExecutor.getQueue());
        System.out.println("拒绝策略:" +poolExecutor.getRejectedExecutionHandler());
        System.out.println("线程工厂:" +poolExecutor.getThreadFactory());
        // 关闭线程池
        poolExecutor.shutdown();
    }
}

运行结果:

修改前:
核心线程数:1
最大线程数:32
线程池大小:0
达到过的最大值:0
正在使用的线程数:0
任务数:0
完成的任务数:0
阻塞队列:[]
拒绝策略:java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy@567d299b
线程工厂:java.util.concurrent.Executors$DefaultThreadFactory@2eafffde
修改后:
核心线程数:10
达到过的最大值:40
线程池大小:1
最大线程数:1
正在使用的线程数:0
任务数:1
完成的任务数:1
阻塞队列:[]
拒绝策略:java.util.concurrent.ThreadPoolExecutor$DiscardPolicy@2aafb23c
线程工厂:java.util.concurrent.Executors$DefaultThreadFactory@2eafffde

Process finished with exit code 0

四、再来探究下几个修改方法

4.1 setCorePoolSize 

先来看Spring提供的ThreadPoolTaskExecutor#setCorePoolSize

 再来看JDK的实现:

    /**
     * Sets the core number of threads.  This overrides any value set
     * in the constructor.  If the new value is smaller than the
     * current value, excess existing threads will be terminated when
     * they next become idle.  If larger, new threads will, if needed,
     * be started to execute any queued tasks.
     *
     * @param corePoolSize the new core size
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     * @see #getCorePoolSize
     */
    public void setCorePoolSize(int corePoolSize) {
        if (corePoolSize < 0)
            throw new IllegalArgumentException();
        int delta = corePoolSize - this.corePoolSize;
        this.corePoolSize = corePoolSize;
        if (workerCountOf(ctl.get()) > corePoolSize)
            interruptIdleWorkers();
        else if (delta > 0) {
            // We don't really know how many new threads are "needed".
            // As a heuristic, prestart enough new workers (up to new
            // core size) to handle the current number of tasks in
            // queue, but stop if queue becomes empty while doing so.
            int k = Math.min(delta, workQueue.size());
            while (k-- > 0 && addWorker(null, true)) {
                if (workQueue.isEmpty())
                    break;
            }
        }
    }

从这个方法,可以知道:

在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。此时:onlyOne:false

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收;

对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务。

大致工作流程如下图:

 4.2 setMaximumPoolSize 

 

 再来看JDK的实现:

    /**
     * Sets the maximum allowed number of threads. This overrides any
     * value set in the constructor. If the new value is smaller than
     * the current value, excess existing threads will be
     * terminated when they next become idle.
     *
     * @param maximumPoolSize the new maximum
     * @throws IllegalArgumentException if the new maximum is
     *         less than or equal to zero, or
     *         less than the {@linkplain #getCorePoolSize core pool size}
     * @see #getMaximumPoolSize
     */
    public void setMaximumPoolSize(int maximumPoolSize) {
        if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
            throw new IllegalArgumentException();
        this.maximumPoolSize = maximumPoolSize;
        if (workerCountOf(ctl.get()) > maximumPoolSize)
            interruptIdleWorkers();
    }

这个方法主要做了几个事情:

  • 参数校验:最大线程数必须大于0且大于等于核心线程数
  • 覆盖原来的最大线程数
  • 判断工作线程如果大于最大线程数,则对空闲线程发起中断请求

4.3 setKeepAliveTime

    /**
     * Sets the time limit for which threads may remain idle before
     * being terminated.  If there are more than the core number of
     * threads currently in the pool, after waiting this amount of
     * time without processing a task, excess threads will be
     * terminated.  This overrides any value set in the constructor.
     *
     * @param time the time to wait.  A time value of zero will cause
     *        excess threads to terminate immediately after executing tasks.
     * @param unit the time unit of the {@code time} argument
     * @throws IllegalArgumentException if {@code time} less than zero or
     *         if {@code time} is zero and {@code allowsCoreThreadTimeOut}
     * @see #getKeepAliveTime(TimeUnit)
     */
    public void setKeepAliveTime(long time, TimeUnit unit) {
        if (time < 0)
            throw new IllegalArgumentException();
        if (time == 0 && allowsCoreThreadTimeOut())
            throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
        long keepAliveTime = unit.toNanos(time);
        long delta = keepAliveTime - this.keepAliveTime;
        this.keepAliveTime = keepAliveTime;
        if (delta < 0)
            interruptIdleWorkers();
    }

当 allowCoreThreadTimeOut 参数设置为 true 的时候,核心线程在空闲了 keepAliveTime 的时间后也会被回收的,相当于线程池自动给你动态修改了。

不允许在allowCoreThreadTimeOut 为true时设置time为0;

4.4  setRejectedExecutionHandler

    /**
     * Sets a new handler for unexecutable tasks.
     *
     * @param handler the new handler
     * @throws NullPointerException if handler is null
     * @see #getRejectedExecutionHandler
     */
    public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
        if (handler == null)
            throw new NullPointerException();
        this.handler = handler;
    }

4.5 setThreadFactory

   /**
     * Sets the thread factory used to create new threads.
     *
     * @param threadFactory the new thread factory
     * @throws NullPointerException if threadFactory is null
     * @see #getThreadFactory
     */
    public void setThreadFactory(ThreadFactory threadFactory) {
        if (threadFactory == null)
            throw new NullPointerException();
        this.threadFactory = threadFactory;
    }

这里和上面拒绝策略一样,只是做了一个判空。

4.6 队列的长度可以修改吗?

   /**
     * The queue used for holding tasks and handing off to worker
     * threads.  We do not require that workQueue.poll() returning
     * null necessarily means that workQueue.isEmpty(), so rely
     * solely on isEmpty to see if the queue is empty (which we must
     * do for example when deciding whether to transition from
     * SHUTDOWN to TIDYING).  This accommodates special-purpose
     * queues such as DelayQueues for which poll() is allowed to
     * return null even if it may later return non-null when delays
     * expire.
     */
    private final BlockingQueue<Runnable> workQueue;

如果我们使用的LinkedBlockQueue,从源码来看:

private final int capacity;

不可修改。

五、其他

其实我们在设置线程池的核心参数的时候,总是需要根据业务的实际情况包括机器性能来多方面考虑,这种动态修改的话,其实我们把线程池的几个核心参数放在配置中,监听配置中心的变化来异步修改线程池来调优。

关于核心线程数,可以额外再关注两个问题:

(1)核心线程数的允许销毁--allowCoreThreadTimeOut置为true. 这一点上面代码就可以看到

(2)线程预热:再没有任务来的时候,里面时不会有活跃线程的。线程预热可以预热全部核心数或者只预热其中一个。

    /**
     * Starts a core thread, causing it to idly wait for work. This
     * overrides the default policy of starting core threads only when
     * new tasks are executed. This method will return {@code false}
     * if all core threads have already been started.
     *
     * @return {@code true} if a thread was started
     */
    public boolean prestartCoreThread() {
        return workerCountOf(ctl.get()) < corePoolSize &&
            addWorker(null, true);
    }
    /**
     * Starts all core threads, causing them to idly wait for work. This
     * overrides the default policy of starting core threads only when
     * new tasks are executed.
     *
     * @return the number of threads started
     */
    public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }

以上。

posted @ 2021-08-17 20:21  未知的九月  阅读(420)  评论(0编辑  收藏  举报