编程之美,从线程池状态管理来看二进制操作之美

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

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

线程池状态

首先,为了文章的完整性,我们还是先了解一下线程池的状态,总结如下如:
在这里插入图片描述

线程池状态分为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变量何许人也

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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种状态如下:具体线程的状态代表的含义和状态的转换,下面会有讲解:

1
2
3
4
5
6
7
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位存储, 上述的几种状态:
底层存储二进制为:

1111 1111 1111 1111 1111 1111 1111 1111(-1)
0000 0000 0000 0000 0000 0000 0000 0000(0)
0000 0000 0000 0000 0000 0000 0000 0001(1)
0000 0000 0000 0000 0000 0000 0000 0010(2)
0000 0000 0000 0000 0000 0000 0000 0011(3)

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

1110 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0010 0000 0000 0000 0000 0000 0000 0000
0100 0000 0000 0000 0000 0000 0000 0000
0110 0000 0000 0000 0000 0000 0000 0000

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

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

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

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

1
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 | 0000 0000 0000 0000 0000 0000 0000 0000
= 1110 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

1
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

1
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的值

方法:

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

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

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

方法:

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

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

最后

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

作者

洋仔

发布于

2021-03-03

更新于

2021-03-03

许可协议


评论