阿里一面:那我把线程池coreSize配置成0会怎样?
写在前面
假如我的项目中有一个非常不重要的链路,偶尔需要执行一下。在线程池设计的时候,我想到了线程池的八股文。于是为了尽可能节约资源,于是我把“常驻”的核心线程数配置成了0,这样的线程池能执行任务吗?
线程池八股文回顾
任务投递时,有以下几种策略:
- 线程池线程数量 < 核心线程数,则创建一个新的线程执行任务
- 线程池线程数量 >= 核心线程数
** 阻塞队列未满:投递任务进阻塞队列中
** 阻塞队列已满
*** 线程池线程数量 < 最大线程数:创建新的线程执行当前任务
*** 线程池线程数量 >= 最大线程数:执行拒绝策略
大概得流程就是如上了,那corePoolSize == 0 的时候,按照上面的八股文,难道会直接投递进阻塞队列中等待执行吗?如果队列够大,会不会任务一直无法执行(因为队列没满,一直也不会创建“空闲线程”)
举个栗子
靠猜是没有用的,我们实践出真知。 下面我配置了一个线程池,期望投递任务的时候,能够通过“空闲线程”短时间帮忙处理,最终自行销毁掉,不持续占用系统资源。 那这种线程池在执行过程中有可能因为阻塞队列未满,最终任务迟迟没有执行吗?
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
0,
8,
5, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(512),
new CallerRunsPolicy()
);
这里使用这个线程池打印一个字符串,看看是否能够执行就好了
public static void main(String[] args) {
MonitorThreadPoolConfig.addThreadPoolExecutor(threadPoolExecutor);
threadPoolExecutor.execute(() -> System.out.println("hahahhaha "));
}
通过执行结果可以得知:任务是会被执行的(这里我加了对线程池的定时埋点监控)
探寻源码
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);
}
其实这个问题也不复杂,从粘贴出来的第34行 - 第35行可以看出:当核心线程数 == 0的时候会创建一个新的线程执行当前的任务
拓展一下
既然可以通过corePoolSize == 0的方式来尽可能减少”常驻“线程的资源占用,那有没有别的办法可以达到同样的效果呢?
八股文战神可能会抢答了:ThreadPoolExecutor自1.6开始有一个属性如下,它的作用是支持核心线程超时销毁
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
private volatile boolean allowCoreThreadTimeOut;
于是我们可以将线程池配置改成如下再试一下(注意我把超时时间改成了5秒钟):
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
8,
5, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(512),
new CallerRunsPolicy()
);
public static void main(String[] args) {
MonitorThreadPoolConfig.addThreadPoolExecutor(threadPoolExecutor);
threadPoolExecutor.allowCoreThreadTimeOut(true); //核心线程数可以超时
threadPoolExecutor.execute(() -> System.out.println("hahahhaha "));
}
执行结果如下,符合我们的预期。可以看到任务正常执行、线程池的线程数量初始是0,随后是1,最后自然销毁了,变成了0。
总结
- 当核心线程数设置为0的时候,在任务投递后,线程池内部会创建一个新的线程来执行任务(当然你的最大线程数配置要大于0)
- 针对功能点执行频率极低的场景,我们可以使用文中描述的两种方式(1. 核心线程数为零 2.
- naallowCoreThreadTimeOut == true)来让线程池临时创建可超时销毁的线程来执行任务
ps:据说JDK1.6之前corePoolSize == 0的话,线程池真的可能会出现投递进阻塞队列后没有线程执行的尴尬。