关于线程池:allowCoreThreadTimeOut(true) 的小知识点
1.背景
检查别人程序发现堆内存2G,但是常驻内存res到3.2G了,经过一系列操作发现线程特别多(1000+), 当然有重复创建线程池的问题。除此之外,由于是数据域应用,为了加速查询,会定时把数据库数据load到缓存,还不部分是应付也个页面N次(20+)查询,希望能支持稍微高一点的并发响应。所以连接池各方面设置比较大。(线程会持续吃RES内存,不释放,默认1M)
2.操作
期望线程数减少,定时任务用了线程线程之后,能回收掉等等操作。allowCoreThreadTimeOut 支持回收核心线程,合适定时任务这种回收、其他操作就不介绍了,这里仅回顾下它的回收过程。
3.基本介绍
代码是:
ThreadPoolExecutor#allowCoreThreadTimeOut(true)
进去是:
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)
// 中断空闲线程,第一次设置调用
// workers 还是空的,不太明白作用
// 因为这个方法是独立,猜测场景是
// 某任务过程条件下,手动调用该方法一次
// 比如shutDown 就是一种条件
interruptIdleWorkers();
}
}
前面都是声明一些东西,还是找到runWorker 执行的地方:
final void runWorker(Worker w) {
// 一顿操作略
// 这里task 获取不到就会跳出循环 @A
while (task != null || (task = getTask()) != null) {
}
// .. 最后会让线程退出
finally {
processWorkerExit(w, completedAbruptly);
}
// 操作略
}
processWorkerExit 代码:
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
从新回去看@A 里面的getTask
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
// 一顿操作 省略
try {
// 这里就用到 allowCoreThreadTimeOut 来判断这个值
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 这个值又会作为 poll 还是 take 的条件
// 由于timed=true,所以我们会用poll
// 然后超过 keepAliveTime 没有活动线程,那么就会返回空,从而触发@A 部分跳出循环,回收线程
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
那么如果 allowCoreThreadTimeOut=false
没超过核心线程数的情况下、或者异常、中断等情况下
就会进入 workQueue.take() 阻塞等待,那么刚才执行已经执行过任务的那部分核心线程就不会被回收了。
小结:
1.虽然线程池用了很久,从1.6 到1.8 都看了下源码,但是还是有些场景和知识点遗忘的地方,仅作为学习记录,加深下印象。
2.关于线程池的应用很多,从低延迟高并发触发,从IO/CPU 出发,从定时任务量、机器资源排队情况等,需要结合经验、压测,以及合理的业务、线程池拆分找到合适的选择