0.综述

  1. ctl 是线程池源码中常常用到的一个变量。
  2. 它的主要作用是记录线程池的生命周期状态和当前工作的线程数。
  3. 作者通过巧妙的设计,将一个整型变量按二进制位分成两部分,分别表示两个信息。

1.声明与初始化

  源码:

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

  分析一波:

  1. ctl (线程池控制状态)是原子整型的,这意味这对它进行的操作具有原子性。
  2. 如此一来,作为 ctl 组成部分的 runState (线程池生命周期状态)和 workerCount (工作线程数) 也将同时具有原子性。
  3. ThreadPoolExecutor 使用  ctlOf 方法来将  runState 和  workerCount 两个变量(都是整型)打包成一个 ctl  变量。稍后将解读这个方法的实现。

2.两个工具人常量 COUNT_BITS 和 CAPACITY

  源码:

 private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

  分析一波:

  1. COUNT_BITS  常量的值为 Integer.SIZE - 3 ,其中 Integer.SIZE 为整型最大位数,在本文剩余部分,我们取其为 32 。
  2. 如此 COUNT_BITS 实际的值其实就是 29 。这里有些读者可能会有 “为什么减去的数是 3 而不是别的” 的疑惑,这将在后文得到解答。
  3. CAPACITY  常量的值为  (1 << COUNT_BITS) - 1 ,其中 << 为左移运算符,这么说可能不太直观,我以二进制直接写出这个数将有助于理解:
    1
    0000 0000 0000 0001
    1 << 29 - 1
    0001 1111 1111 1111
  4. 因此在接下来的代码中, COUNT_BITS 就用来表示分隔runState 和workerCount 的位数;
  5. 而CAPACITY 则作为取这两个变量( runState 和 workerCount )的工具(具体是怎么使用的请看下文)

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;

  分析一波:

  1. 这里解答了上边关于 COUNT_BITS 变量为什么要减 3 的问题:因为线程池的生命周期有 5 个状态,为了表达这 5 个状态,我们需要 3 个二进制位。
  2. 线程池的生命周期有兴趣的读者请百度 线程池生命周期 ;不明白为什么 5 个状态需要 3 个二进制位的请百度 二进制 。
  3. 注意到这里标注状态使用的并不是 -1 ~ 3 ,而是这 5 个数字分别左移 COUNT_BITS 位,这样做的好处将在接下来的代码中得到体现。

4.打包函数与拆包函数

  源码:

 //拆包函数
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; }

  分析一波:

  1. 此处我们解答了 CAPACITY 常量的作用,之前提到过,他是一个后 29 位均为 1 ,前 3 位为 0 的整数,因此我们可以通过:
  2. 对 CAPACITY 和 ctl 进行 & (按位与)操作就能取到 ctl 的后 29 位,即  workerCount 。
  3. 对 CAPACITY 进行 ~ (按位取反)操作后,再和 ctl 进行 & 操作就能取到 runState 。它的高 3 位是 ctl 的高 3 位,低 29 位为 0。这也解释了为什么之前提到的生命周期常量要在 -1 ~ 3 的基础上再左移 29 位,因为不在常量初始化处左移的话就要在拆包的时候右移来保证取到的是正确的数值。然而拆包操作是要经常进行的,而常量的初始化只有一次。两下对比,明显在初始化时左移是效率更高的选择。
  4. 除了拆包时的效率,常量初始化时左移也提高了打包函数的效率:此处打包函数可以直接对 runState 和 workerCount 进行 | (按位或)操作来得到 ctl 变量,就是因为 runState 的高 3 位为有效信息,而 workerCount 的低 29 位为有效信息,合起来正好得到一个含 32 位有效信息的整型变量。
  5. 说到这里可能仍有些让人疑惑,我将再以二进制的形式表示出所有涉及到的变量/常量:
    //下文中a和b分别代表runState和workerCount的有效信息
    
    //CAPACITY
    0001 1111 1111 1111
    //ctl
    aaab bbbb bbbb bbbb
    //runState
    aaa0 0000 0000 0000
    //workerCount
    000b bbbb bbbb bbbb

5.运行状态的判断

  源码:

     private static boolean runStateLessThan(int c, int s) {
return c < s;
} private static boolean runStateAtLeast(int c, int s) {
return c >= s;
} private static boolean isRunning(int c) {
return c < SHUTDOWN;
}

  分析一波:

  1. 注意这里传入的s是用了之前定义的生命周期常量。
  2. 这里判断状态的大小时,直接将c 和s 进行了比较,这是因为代表状态的信息占据了两个变量的高 3 位,而比较高位的大小时,低位是没有影响的。

6.修改ctl中workCount的大小

  源码:

     private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
} private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
} private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}

  分析一波:

  1. 注意到这里的修改都使用了原子整型的CAS方法。

7.修改ctl中runState的大小

  源码:

 ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))

  分析一波:

  1. 注意到修改 runState 并没有再提供专门的方法,而是直接使用了原子整型的CAS方法来替换原来的 ctl 。

8.仍存在的疑问

  • Q1:如果经过递增 compareAndIncrementWorkerCount ,使得 workerCount 的大小超过29位,会发生什么?会有安全检查吗?
  • Q2:为什么为 workerCount 的修改提供了方法,却没有为 runState 的修改提供?

详解Java线程池的ctl(线程池控制状态)【源码分析】的更多相关文章

  1. 详解Java中的clone方法 -- 原型模式 及源码

    http://www.cnblogs.com/cq-home/p/6431426.html http://blog.csdn.net/zhangjg_blog/article/details/1836 ...

  2. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)

    我们以ByteArrayInputStream,拉开对字节类型的“输入流”的学习序幕.本章,我们会先对ByteArrayInputStream进行介绍,然后深入了解一下它的源码,最后通过示例来掌握它的 ...

  3. java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)

    前面学习ByteArrayInputStream,了解了“输入流”.接下来,我们学习与ByteArrayInputStream相对应的输出流,即ByteArrayOutputStream.本章,我们会 ...

  4. Java 序列化和反序列化(三)Serializable 源码分析 - 2

    目录 Java 序列化和反序列化(三)Serializable 源码分析 - 2 1. ObjectStreamField 1.1 数据结构 1.2 构造函数 2. ObjectStreamClass ...

  5. Java 序列化和反序列化(二)Serializable 源码分析 - 1

    目录 Java 序列化和反序列化(二)Serializable 源码分析 - 1 1. Java 序列化接口 2. ObjectOutputStream 源码分析 2.1 ObjectOutputSt ...

  6. Java高并发之无锁与Atomic源码分析

    目录 CAS原理 AtomicInteger Unsafe AtomicReference AtomicStampedReference AtomicIntegerArray AtomicIntege ...

  7. 【Java】NIO中Selector的select方法源码分析

    该篇博客的有些内容和在之前介绍过了,在这里再次涉及到的就不详细说了,如果有不理解请看[Java]NIO中Channel的注册源码分析, [Java]NIO中Selector的创建源码分析 Select ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. 线程池ThreadPoolExector核心ctl, execute, addWorker, reject源码分析

    线程池核心方法execute()解析: public void execute(Runnable command) {//#1 if (command == null) throw new NullP ...

  10. 【Java学习笔记之三十三】详解Java中try,catch,finally的用法及分析

    这一篇我们将会介绍java中try,catch,finally的用法 以下先给出try,catch用法: try { //需要被检测的异常代码 } catch(Exception e) { //异常处 ...

随机推荐

  1. Python Ethical Hacking - WEB PENETRATION TESTING(4)

    CRAWING SPIDER Goal -> Recursively list all links starting from a base URL. 1. Read page HTML. 2. ...

  2. P4158 [SCOI2009]粉刷匠(洛谷)

    今天A了个紫(我膨胀了),他看起来像个贪心一样,老师说我写的是dp(dp理解不深的缘故QWQ) 直接放题目描述(我旁边有个家伙让我放链接,我还是说明出处吧(万一出处没有了)我讲的大多数题目都是出自洛谷 ...

  3. 高效C++:继承和实现

    如何正确的使用继承和实现是本章说明的重点. 确定public继承的关系是is-a public继承等同于is-a 对public继承,所有base的特性,在derived上都适用 避免遮掩继承而来的名 ...

  4. 设计模式:proxy模式

    目的:为其他对象提供一种代理以控制对这个对象的访问 理解:尽管Decorator的实现部分与代理相似,但Decorator的目的不一样.Decorator为对象添加一个或多个功能,而代理则控制对对象的 ...

  5. 题解 洛谷 P4189 【[CTSC2010]星际旅行】

    一个比较直接的想法就是对每个点进行拆点,拆成入点和出点,限制放在入点和出点相连的边上,然后跑最大费用最大流即可. 但是这样复杂度无法接受,所以考虑模拟费用流来解决本题. 发现 \(H\) 都大于等于该 ...

  6. C#串模板

    c# 6.0 的语言特性,功能类似string.formate,更方便的地方在于不要像format一样使用索引,可以直接使用变量. 使用方法如下: string name = "zhangs ...

  7. Mybatis(四)多表操作

    数据库如下: 一.创建数据库所对应的bean类 public class User { private Integer uId; private String username; private St ...

  8. 面试题二十二:链表中倒数第k个节点

    方法一:双指针法定义两个指针A.B,A先走k-1步后再一起走,直到A.next==null注意: 1.链表为空 2.链表长度小于k 3.k<=0 当题目是求链表的中间节点时,可以两个指针从开头开 ...

  9. state实例

    States是SaltStack中的配置语言,在日常进行配置管理时需要编写大量的States文件. 比如我们需要安装一个包,然后管理一个配置文件,最后保证某个服务正常运行. 这里就需要我们编写一些st ...

  10. MacOS入门

    原文池建强的blog 对普通用户来说,用好Mac主要有三点: 1.理解OSX的基本结构和特点 2.掌握多手势和快捷键(少量即可,多多益善) 3.用好工具 一.理解OSX的基本结构和特点 Mac OS ...