面试官问线程池哪些参数是可以在运行阶段修改的?
一、首先一张图先回忆下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; }
以上。