线程池多余的线程是如何回收的?

在这里插入图片描述
不过,我倒是对线程池是如何回收工作线程比较感兴趣,所以简单分析了一下,加深对线程池的理解吧。

1 runWorker(Worker w)

工作线程启动后,就进入runWorker(Worker w)方法。里面是一个while循环,循环判断任务是否为空,若不为空,执行任务;若取不到任务,或发生异常,退出循环,执行processWorkerExit(w, completedAbruptly);在这个方法里把工作线程移除掉。

取任务的来源有两个:一个是task = w.firstTask;这个是工作线程第一次跑的时候执行的任务,最多只能执行一次;后面得从getTask()方法里取任务,看来getTask()是关键,在不考虑异常的场景下,返回null,就表示退出循环,结束线程。下一步,就得看看,什么情况下getTask()会返回null。

 try {
     while (task != null || (task = getTask()) != null) {
         ...
     }
     completedAbruptly = false;
 } finally {
     processWorkerExit(w, completedAbruptly);
 }

2 getTask() 返回null

    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

第一种情况,线程池的状态已经是STOP,TIDYING, TERMINATED;或者是SHUTDOWN且工作队列为空;

 // Check if queue empty only if necessary.(仅在必要时检查队列是否为空)
  if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
      decrementWorkerCount();
      return null;
  }

第二种情况,工作线程数已经大于最大线程数或当前工作线程已超时,并且,还有其他工作线程或任务队列为空。

  if ((wc > maximumPoolSize || (timed && timedOut))
          && (wc > 1 || workQueue.isEmpty())) {
      if (compareAndDecrementWorkerCount(c))
          return null;
      continue;
  }

3 分场景分析线程池回收工作线程

3.1 RUNNING状态下全部任务执行完成的场景

这种场景会将工作线程的数量减少到核心线程数大小(如果本来就没有超过,则不需要回收)。

假设一个线程池,核心线程数为4,最大线程数为8。一开始是4个工作线程,任务队列已经塞满,就需要将工作线程增加到8。当后面任务执行到差不多了,线程取不到任务了,就会回收到4个工作线程的状态(取决于allowCoreThreadTimeOut的值,这里讨论默认值false的情况,即核心线程不会超时。如果为true,工作线程可以全部销毁)。

可以先排除上面提到的条件1:线程池的状态已经是STOP,TIDYING, TERMINATED;或者是SHUTDOWN且工作队列为空;因为线程池一直是RUNNING,这条判断永远是false。在这个场景中,可以当条件1不存在。

下面分析取不出任务时线程是怎么运行的。

1、从任务队列取任务有两种方式:
—————————————————————————————————————————————————————————————

Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

如果timed是true,那么走workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)也就是设置获取任务的超时时间,到时间后还没获取到任务的话则会timeOut=true。

try {
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}

timeOut=true的话,getTask有个判断会让其跳出循环,线程生命周期也自然而然的随之结束。

if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
        return null;
    continue;
}

反之如果timed是false的话,那么会执行workQueue.take();不带超时时间的,则一直阻塞等待有结果返回。

其实一句话就概括了:从队列中获取任务,如果timed是true的话,则调用阻塞队列的poll方法阻塞一段时间获取任务,这段时间没任务的话,则超时设置timeOut=true,结束生命周期。否则调用take()方法一直阻塞等待任务到来,也就是核心线程为什么能一直存活的原因。
—————————————————————————————————————————————————————————————
在线程超时等待唤醒之后,发现取不出任务,timeOut变为true,进入下一次循环。

2、来到条件1的判断,线程池一直RUNNING,不进入代码块。

 // Check if queue empty only if necessary.(仅在必要时检查队列是否为空)
  if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
      decrementWorkerCount();
      return null;
  }

3、来到条件2的判断,这时任务队列为空,条件成立,CAS减少线程数,若成功,返回null;否则,重复step1。

  if ((wc > maximumPoolSize || (timed && timedOut))
          && (wc > 1 || workQueue.isEmpty())) {
      if (compareAndDecrementWorkerCount(c))
          return null;
      continue;
  }

这里要注意,有可能多条线程同时通过条件2的判断,那会不会减少后线程的数量反而比预想的核心线程数少呢?比如当前线程数已经只有5条了,此时有两条线程同时唤醒,通过条件2的判断,同时减少数量,那剩下的线程数反而只有3条,和预期不一致。

实际上是不会的。为了防止这种情况,compareAndDecrementWorkerCount© 用的是CAS方法,如果CAS失败就continue,进入下一轮循环,重新判断。像上述例子,其中一条线程会CAS失败,然后重新进入循环,发现工作线程数已经只有4了,timed为false, 这条线程就不会被销毁,可以一直阻塞了(workQueue.take())。

从这里也可以看出,虽然有核心线程数,但线程并没有区分是核心还是非核心,并不是先创建的就是核心,超过核心线程数后创建的就是非核心,最终保留哪些线程,完全随机。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值