ThreadPoolExecutor中的keepAliveTime解析

keepAliveTime是线程池中空闲线程等待任务的时间,如果超出这个时间,线程池就会将当前线程从线程池中踢出

分为两种场景:

  1. allowCoreThreadTimeout设置为true,线程池中空闲线程超时后,getTask()返回null,当前worker线程就会从线程池workers中踢出;
  2. 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)方法实现永久阻塞,该方法退出有两种方式:

  1. 调用LockSupport.unpark(this)方法;
  2. 调用该线程的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有两种情况:

  1. allowCoreThreadTimeout设置为true,不管是不是核心线程,当超时后,getTask()返回null,当前worker就会从线程池workers中去除;
  2. allowCoreThreadTimeout设置为false,如果maximumPoolSize设置大于corePoolSize,当前线程池里面的线程数大于corePoolSize,就会调用超时api获取任务;

写博客主要是对自己看源码过程的一个总结感悟,如有不足或错误的地方,请大家评论区批评指正~ 😃

  • 29
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值