线程池ThreadPoolExecutor中ctl是什么?

线程池在java编程语言中的重要性就不言而喻了,对于线程池底层的实现可能大家的研究就没有那么深入了,下面针对线程池ThreadPoolExecutor中的最重要的一个成员变量ctl,来做一个介绍!


ThreadPoolExecutor中ctl介绍

先看一下注释,原文如下

/**
 * The main pool control state, ctl, is an atomic integer packing
 * two conceptual fields
 *   workerCount, indicating the effective number of threads
 *   runState,    indicating whether running, shutting down etc
 */

简单翻译一下,就是 ctl是一个线程安全的int变量,用于存储了两个概念的字段,第一个是当前线程池中的工作线程数,另外一个是当前线程池的状态。

线程池的5种状态

*   RUNNING:  Accept new tasks and process queued tasks
接受新任务并处理队列中的任务


*   SHUTDOWN: Don't accept new tasks, but process queued tasks
不接受新任务,但处理队列中的任务


*   STOP:     Don't accept new tasks, don't process queued tasks,
*             and interrupt in-progress tasks

不接受新任务,不处理队列中的任务,并中断正在进行的任务


*   TIDYING:  All tasks have terminated, workerCount is zero,
*             the thread transitioning to state TIDYING
*             will run the terminated() hook method

所有任务都已终止,workerCount为零,过渡到TIDYING状态的线程将运行terminated()钩子方法


*   TERMINATED: terminated() has completed

terminated()已完成

那么问题来了,用一个int变量怎么同时表示两个业务含义的数据呢?

为什么要这么设计?

先看下线程池中五个状态位是怎么定义的吧

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    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;

因为线程池作为一个稳定的常用工具,其状态不会频繁的变动,5种状态已经能完成其生命周期内的所有工作了,想一想,5种状态可以用多少位二进制表示?很显然大于5的最小的2的整数次幂是8,即用3位bit就能表示线程的状态了。

int在java中是占用4个字节,一个字节8bit,也就是一个int占用32bit,已经可以用3位bit来表示,那么剩下的29bit就是用来表示工作线程数了,那么,是怎么做到的呢?

常量分析

  • COUNT_BITS

这个毫无疑问是29了,因为int是占用32个二进制位,32-3就是29了

  • CAPACITY

这里用到了<<左移运算,将1向左移动29位,可以想象下,得到的结果是不是这样

1000....000  【1后面跟着29个0】,对应的数值就是2的29次幂

然后,对结果减一,是不是得到的结果就是

111.....1111【29个1】

因为int是32位bit,补齐就是

000 1111.....11111 【29个1】

对应的大小是2的29次幂,说明在线程池中,最大能创建的线程的个数是2的29次幂,而并非是int的最大值。

  • RUNNING

这里补充一点知识,在计算机中数字存储的是其对应的补码,那么补码是怎么计算得出的呢?

先计算出原码,然后根据原码计算出反码,最后再根据反码得出补码,之所以计算机中要使用补码是因为如下两个原因

1.方便加减运算

2.使用补码,只有一个0,如果使用原码,将会有+0和-0,将会给计算带来复杂性

正数的原码,反码,补码一样

负数的原码,首位是1表示符号位,反码是保留原码的符号位,剩下的位取反操作,得到的结果就是反码,然后将反码+1就得到了补码

可以看到-1的补码就是32个1,然后左移29位,得到的结果就是,结果肯定是一个负数

111000...000【29个0】

  • SHUTDOWN

毫无疑问,对应的shutdowm的二进制是

000000....000【32个0】

  • STOP

这个也很简单,对应的二进制是

001 000....000【连续29个0】

  • TIDYING

这个也很简单,对应的二进制是

010 000....000【连续30个0】

  • TERMANATED

这个也很简单,对应的二进制是

011 000....000【连续29个0】

线程池状态判断

从这里可以看到,这个设计的目的就是用int的高三位来表示线程池的状态为,那么怎么根据一个int数值来判断当前线程池的状态呢?

private static int runStateOf(int c)     { return c & ~CAPACITY; }

 解释下,

CAPACITY的补码是000 1111.....11111 【29个1】

先对~CAPACITY取反,很显然得到的结果是

111 000...000【29个0】

& c

结果就很明了,因为CAPACITY取反后低29位都是0,只比较高三位,高三位和线程池中哪一个状态位相同,即当前线程池的状态

工作线程数计算

这个也是通过位运算来实现的,看下代码

private static int workerCountOf(int c)  { return c & CAPACITY; }

解释下

直接用CAPACITY的补码

000 1111.....11111 【29个1】

& c

结果就很明了,因为CAPACITY的高3位是0,&运算有效的就是低29位,而低29位就是当前线程池中的工作线程数了

工作线程数和线程池状态怎么关联起来? 

既然,一个int同时表示了两个业余含义,恳请需要有一个关联的方法,比如,当前线程池的状态是RUNNING,并且工作线程数是3个,怎么表示呢?

private static int ctlOf(int rs, int wc) { return rs | wc; }

解释下

|运算,你可以理解为10进制中的加法运算

比如上面的例子中就可以将

rs【线程池的状态RUNNING】111000...000【29个0】

|

wc【工作线程数3】000....0011【连续30个0】

得到的结果就是

111000....011【中间连续27个0】

至此,ctl的设计已经清晰明了了,通过使用一个int就能同时表示两个业务变量,不得不对Doug Lea老爷子表示敬意! 

总结

通过上面案例的分析,想必大家对线程池中的ctl设计已经有所了解了。

不过,在实际的业务开发中,我们还是要尽量避免这种写法,因为这样操作可能减少了变量,但是却增加了业务代码的复杂度,我们要好好思量下,一定要做好权衡!

如果你是中间件、通用组件开发,对时间复杂度和空间复杂度有相当高的要求,那自然另当别论了,另外,这种用二进制位存储的思想,还可以用来表示一个业务同时存在的属性,详情参考利用位运算实现一个字段表示多个属性-CSDN博客

猜你喜欢

转载自blog.csdn.net/tianmlin1/article/details/135105491