keepAliveTime是线程池中空闲线程等待任务的时间,如果超出这个时间,线程池就会将当前线程从线程池中踢出
分为两种场景:
- allowCoreThreadTimeout设置为true,线程池中空闲线程超时后,getTask()返回null,当前worker线程就会从线程池workers中踢出;
- allowCoreThreadTimeout设置为false,maximumPoolSize设置大于corePoolSize,如果当前线程池里面的线程数大于corePoolSize,空闲线程就会调用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)获取任务,超时还获取不到任务,就会把这个线程从线程池workers中踢出,直到线程池中线程数小于等于corePoolSize,此时空闲线程就会调用workQueue.take()方法永久阻塞,等待任务;
keepAliveTime一共有4个引用的地方,我们先挑软柿子捏,来一起看看都是怎么用的。
keepAliveTime
getKeepAliveTime
public long getKeepAliveTime(TimeUnit unit) {
return unit.convert(keepAliveTime, TimeUnit.NANOSECONDS);
}
so easy,如果有业务需要的话,支持直接获取线程池的keepAliveTime,并且是以纳秒单位返回
allowCoreThreadTimeout
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0) // @1
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers();
}
}
@1
如果allowCoreThreadTimeout设置为true,就必须设置keepAliveTime大于0,否则就会抛出异常
顺便说一下这个方法,当allowCoreThreadTimeout从false改成true,为什么需要调用interruptIdleWorkers方法呢?
我们可以思考一下,在线程池中什么样的线程算是空闲线程呢?就是当前没有处理任务,被阻塞队列挂起的线程,可以看下面的代码片段。
private Runnable getTask() {
// 此处省略一些逻辑
for (; ; ) {
// 此处省略一些逻辑
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask方法就是空闲线程获取任务的方法,当allowCoreThreadTimeout为false的时候,调用的是workQueue.take()方法,即永久等待任务,当改成true之后,线程池线程是要调用阻塞队列超时api获取任务的,从代码上看,要想退出workQueue.take()永久阻塞方法,只有抛出InterruptedException异常,重新执行for循环,才可以有机会调用阻塞队列超时api获取任务,所以interruptIdleWorkers方法肯定是可以实现这个功能的。
在interruptIdleWorkers方法中调用了t.interrupt()方法,那就会想为什么调用t.interrupt()方法,就可以使workQueue.take()方法抛出InterruptedException异常呢?对AQS了解的小伙伴应该知道,workQueue.take()方法在内部调用了LockSupport.park(this)方法实现永久阻塞,该方法退出有两种方式:
- 调用LockSupport.unpark(this)方法;
- 调用该线程的t.interrput()方法,抛出InterruptedException;
综上,当allowCoreThreadTimeout从false改成true,调用interruptIdleWorkers方法,getTask();中的workQueue.take()就抛出InterruptedException异常,重新执行for循环,就可以调用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)方法了。
interrputIdleWorkers方法详细解释可以参考这篇文章
AQS可以参考这篇文章
setKeepAliveTime
public void setKeepAliveTime(long time, TimeUnit unit) {
if (time < 0) // @1
throw new IllegalArgumentException();
if (time == 0 && allowsCoreThreadTimeOut()) // @2
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(); // @3
}
@1
这个很好理解,keepAliveTime的时间要大于等于0
@2
如果allowCoreThreadTimeout配置为true的话,keepAliveTimeout的时间必须要大于0
@3
因为setKeepAliveTime方法的作用域是public,在程序运行过程中,可以调用这个方法修改keepAliveTime的值,如果改小,同样也会调用interruptIdleWorkers()方法。
为什么keepAliveTime变小要调用interruptIdleWorkers方法呢?
实际和上面1.2.1中allowCoreThreadTimeout从false改成true一样,要刷新workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)中的keepAliveTime。通过调用interruptIdleWorkers()中的t.interrupt()方法让getTask中的workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)抛出InterruptedException异常,重新执行for循环,刷新keepAliveTime。
那我设置大了,为什么不直接刷新呢?
因为到达keepAliveTime之后,本身就会重新执行for循环,如果队列里面还没有任务,自然就会使用更大的keepAliveTime阻塞等待任务。
getTask
private Runnable getTask() {
// 此处省略一些逻辑
for (; ; ) {
// 此处省略一些逻辑
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // @1
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
@1
从代码可以看出,当timed为true时,调用有超时时间的api从阻塞队列中获取任务,timed为true有两种情况:
- allowCoreThreadTimeout设置为true,不管是不是核心线程,当超时后,getTask()返回null,当前worker就会从线程池workers中去除;
- allowCoreThreadTimeout设置为false,如果maximumPoolSize设置大于corePoolSize,当前线程池里面的线程数大于corePoolSize,就会调用超时api获取任务;
写博客主要是对自己看源码过程的一个总结感悟,如有不足或错误的地方,请大家评论区批评指正~ 😃