阿里一面:那我把线程池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。

总结

  1. 当核心线程数设置为0的时候,在任务投递后,线程池内部会创建一个新的线程来执行任务(当然你的最大线程数配置要大于0)
  2. 针对功能点执行频率极低的场景,我们可以使用文中描述的两种方式(1. 核心线程数为零 2.
  3. naallowCoreThreadTimeOut == true)来让线程池临时创建可超时销毁的线程来执行任务

ps:据说JDK1.6之前corePoolSize == 0的话,线程池真的可能会出现投递进阻塞队列后没有线程执行的尴尬。

posted @ 2024-12-24 00:42  瓶子coder  阅读(52)  评论(0编辑  收藏  举报