【Java】编程之美!从线程池状态管理来看二进制操作之美

【Java】编程之美!从线程池状态管理来看二进制操作之美

二进制操作在框架设计中被频繁使用,使用二进制在不同场景有提升计算速度较少内存占用等多种优点;

下面,我们依据线程池的状态管理,来看下怎么通过操作二进制对状态进行管理,过程中会发现编程之美~

线程池状态

首先,为了文章的完整性,我们还是先了解一下线程池的状态,总结如下如:
【Java】编程之美!从线程池状态管理来看二进制操作之美

线程池状态分为5种:RUNNINGSHUTDOWNSTOPTIDYINGTERMINATED

状态代表的含义

  • RUNNING:(运行)接收新task,并且处理正在排队的task,不中断正在执行的任务
  • SHUTDOWN:(关闭)不接受新的task,只处理正在排队的task,不中断正在执行的任务
  • STOP:(停止)不接受新的task,也不处理正在排队的task,并且中断正在执行的任务
  • TIDYING:(整理)所有的task都已经终止,上述提到的workCount当前活跃线程数为0,被中断的任务和正在排队的任务执行当前任务的terminated()钩子方法
  • TERMINATED:(已终止)标识上述的TIDYING的过程结束,标识当前线程池成功完全停止的状态

状态转换

大致的流程就是:

RUNNING --> SHUTDOWN --> STOP --> TIDYING --> TERMINATED

上述流程是一个单方向的顺序,也就是说不会出现类似于STOP --> SHUTDOWN 这种情况;

另外,并不是每一个状态多必须经过的;

什么时候进行线程池的状态转换呢?

  • RUNNING -> SHUTDOWN:调用终止线程的方法shutdown()
  • RUNNING or SHUTDOWN -> STOP:调用shutdownNow()方法后,不管当前在RUNNING状态还是SHUTDOWN状态,都是直接转为STOP状态
  • SHUTDOWN -> TIDYING:SHUTDOWN状态下当等待队列 和 正在执行的任务 都为空时,状态转为TIDYING
  • STOP -> TIDYING:STOP状态下当正在执行的任务全部中断完毕后,状态转为TIDYING
  • TIDYING -> TERMINATED:TIDYING状态下当所有的terminated()钩子方法全部执行完毕后,状态转为TERMINATED,线程池关闭完毕!

管理线程池状态

线程池中管理线程池状态 和 线程池当前活跃线程数,是通过一个AtomicInteger变量来管理这两个状态的

什么? 一个变量管理两个这么不相干的状态? 对的;

CTL变量何许人也

让我们来看一下线程池针对这部分的实现:

    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;

// Packing and unpacking ctl

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

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

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

private static boolean isRunning(int c) { return c < SHUTDOWN;}

下面,我们来剖析一下上述的实现:
线程池包含5种状态如下:具体线程的状态代表的含义和状态的转换,下面会有讲解:

    private static final int COUNT_BITS = Integer.SIZE - 3;

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;

我们知道在java中 int 类型占用4个字节32位存储, 上述的几种状态:
底层存储二进制为:

左移<<COUNT_BITS位 COUNT_BITS = Integer.SIZE - 3 也就是 COUNT_BITS = 29,改句子说明用32位的前3位存储线程池的状态
后29位存储线程池中当前线程的个数, << COUNT_BITS后,变为下面的二进制:

我们可以看到,前三位存储的是 标识线程状态的二进制

对于初始化存储这些状态的变量 AtomicInteger ctl

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0))

初始化AtomicInteger变量ctl,其中ctlOf(RUNNING, 0) 代码为:

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

其中rs标识线程池当前状态,wc为work count标识当前工作线程的数量

上述传入的是ctlOf(RUNNING, 0) ,当前状态为RUNING也就是1110 0000 0000 0000 0000 0000 0000 0000 ,wc为0,也就是当前工作线程数为0,其二进制为0000 0000 0000 0000 0000 0000 0000 0000 ,做"|"或操作,即

上述得到的结果1110 0000 0000 0000 0000 0000 0000 0000就标识,当前线程池状态为RUNNING,线程池活跃线程个数为0!

如何管理?

通过上述创建的ctl变量获取 线程池当前状态 和 线程中活跃线程个数 这两个状态:

获取线程池当前状态,我们可以想一下该如何获取呢? 现在知道的是ctl的前3位是线程池的状态,那我们直接构造一个前三位为1,后29位为0的int即可,然后取余就可以了呗,下面看下源码的实现,就是如此:
使用方法runStateOf

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

其中CAPACITY = (1 << COUNT_BITS) - 1 转化为二进制为:
0001 1111 1111 1111 1111 1111 1111 1111
取反"~"后,二进制为:
1110 0000 0000 0000 0000 0000 0000 0000
也就是将前3位全部变为1,后面全部变为0;
接下来,传入的ctl变量和~CAPACITY做“&”操作,只会保留ctl变量的前3位变量,后29位变量全部为0;

例如:一个标识当前状态为STOP状态的线程池和当前活跃线程数为3的ctl变量为:
0010 0000 0000 0000 0000 0000 0000 0011
和上述得到的1110 0000 0000 0000 0000 0000 0000 0000做“&”操作后得到:
0010 0000 0000 0000 0000 0000 0000 0000 和上述分析的STOP的状态的二进制相同! 即获得了当前线程的状态!

获取线程池当前状态,也很简单,我们知道ctl变量的32的后29位存储的是当前活跃线程数,直接构造一个前三位为0,后29位为1的int即可,然后取余就可以获取到了
使用方法workerCountOf

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

上述知道CAPACITY为:0001 1111 1111 1111 1111 1111 1111 1111

例如:一个标识当前状态为STOP状态的线程池和当前活跃线程数为3的ctl变量为:
0010 0000 0000 0000 0000 0000 0000 00110001 1111 1111 1111 1111 1111 1111 1111 取与后:
0000 0000 0000 0000 0000 0000 0000 0011
标识当前线程池中活跃线程数量为3!

一些方法

1、计算ctl的值

方法:

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

其中,入参rs代表当前线程状态,wc代表当前活跃线程数,取“|”或即可
上述代码不出现问题的前提是:rs只使用的前3位,wc只使用了后29位!

2、判断当前线程池是否正在运行

方法:

private static boolean isRunning(int c) {  return c <小于SHUTDOWN;}值即可!

上述我们知道,5中状态只有RUNNING小于0,SHUTDOWN状态等于0,其他的都是大于0的,所以我们直接把给定的ctl值小于SHUTDOWN值即可!

最后

上述,我们介绍了 线程池的状态 管理部分,主要通过不同位置的二进制来进行标识不同的状态,工作学习还会发现更多巧妙美妙的设计,等待着作为程序员我们去发现;

以上是 【Java】编程之美!从线程池状态管理来看二进制操作之美 的全部内容, 来源链接: utcz.com/a/92674.html

回到顶部