看ThreadPoolExecutor源码前的骚操作

最近一个月看了学习了很多关于SQL性能优化、Spring核心源码分析、MyBatis核心源码分析、JUC并发包下面的知识点,感觉收获很多。这几天,会陆陆续续产出一些博客,进行知识总结。一边健忘一边学习新知识点,痛苦并快乐着。

saoqi.png

Flag

ThreadPoolExecutor(线程池),大家使用线程的时候,都用过它对线程进行创建及其调度管理,想必再熟悉不过。

当我们点开ThreadPoolExecutor源码,看到这一幕,大多数人第一感觉会跟我一样:wtf,这是什么东西。其实仔细看一下,不难理解。

    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; }

AtomicInteger这个类是基于CAS,是解决在高并发情况下原生的int类型自增线程不安全的问题。AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); ctl代表ThreadPoolExecutor中的控制状态。默认情况下ctl代表是RUNNING状态。

int类型占32位,显而易见Integer.size等于32。那么COUNT_BITS等于29。再看到(1 << COUNT_BITS) - 1这一行,说明正数1向左边移动了29位。想必看到这里,大学学的原码,补码,反码,十进制与二进制,运算符都忘光了吧。我们来回顾一下运算符的知识点。

运算符

  • Java支持的位运算符有7个:
    • &:按位与。当两位同时为1时才返回1
    • |:按位或。只要有一位为1即可返回1
    • ~:按位非。单目运算符,将操作数的每个位(包括符号位)全部取反。
    • ^:按位异或。当两位相同时返回0,不同时返回1
    • <<:左移运算符。
    • >>:右移运算符。
    • >>>:无符号位右移运算符。比如-5>>>2-5无符号右移动2位后,左边空出2位后,空出来的2位用0去补充。

原码,反码,补码

在计算机中,数据都以补码的形式存在的,数据在计算机中都是以二进制存在的。计算机存储数据是以字节为单位。

无符号数来说没有原码,反码,补码之分,因为都相同。正数的原码,反码,补码都相同。比如+0的原码是00000000,补码是00000000,补码是00000000。(计算机字长为8)

负数的反码是对该负数的原码(除了符号位)进行按位取反。比如-0的原码是10000000,反码是11111111(计算机字长为8)

负数的补码 = 负数的反码 + 1,比如-0的反码是11111111,在最后一位进行加1,就变成了00000000。即-0的补码为00000000

记住计算机规则都是满则进一位。

计算机中为什么使用补码来存储数据

1.看到上面的例子就懂了。+0的原码和反码和-0的原码和反码都不一样,而它们的补码都是一样的。就是解决了这种不一致的问题。

2.符号位和其他有效值一起进行处理。计算机不适合做减法,用加法代替减法。【a - b】补码= 【a】补码+ 【-b】补码,这里值得注意的是,有的计算机中是有乘法器的,有的计算机是用加法代替乘法。

有趣的例子

看到下面java代码,结果是多少呢? 答案:-1。首先我们要记住一点就是,-1在计算中补码表示每位都是1。java中,-1的二进制表示是11111111111111111111111111111111,也就是32个1。

/**
 * @author cmazxiaoma
 * @version V1.0
 * @Description: TODO
 * @date 2018/8/16 20:01
 */
public class Test {

    public static void main(String[] args) {
        System.out.println(~0);
    }
}

0的补码是00000000000000000000000000000000,~0 的意思对0进行按位取反(包括最高位),取反后的结果应该是11111111111111111111111111111111。我们可以发现这是一个负数,负数的反码 = 补码 - 1,我们计算出反码 = 11111111111111111111111111111110。我们对反码进行按位取反(除了最高位),结果是10000000000000000000000000000001。最后原码进行二进制到十进制的转换,结果是 -1。

想必有了这些基础,再去分析ThreadPoolExecutor源码中的成员变量,应该会轻松很多。

回去分析源码

 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

1的补码是00000000000000000000000000000001,向左移动29个位置,应该变成0010000000000000000000000000000。然后再减去 - 1,通过前面的基础,我们知道-1的补码是11111111111111111111111111111111。2个数值相加的结果是00011111111111111111111111111111。即capacity的值,我们会发现高3位都是0,低29位都是1。

通过一系列的运算,我们可以计算出。 RUNNING = 11100000000000000000000000000000 SHUTDOWN = 00000000000000000000000000000000 STOP = 0010000000000000000000000000000 TIDYING = 0100000000000000000000000000000 TERMINATED = 0110000000000000000000000000000

按数值从小到大排序就是RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATE

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

我们计算workerCount的值是通过c & CAPACITY,也就是按位与。明显是capacity的前3位是000,与c的前3位进行按位与的话,也都000。所以workerCount占据ctl变量的低29位。

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

我们计算runState的值是通过先将capacity进行按位非的操作,然后再和c进行按位与的操作~capacity后的值高3位是111,低29位都是0。c的低29位和~capacity的低29位的计算结果都是0,可以忽略。实际参与计算的是~capacity是高3位(都是1)和c的高3位。所以说runState占据ctl变量的高3位。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 private static int ctlOf(int rs, int wc) { return rs | wc; }

我们看到ctl初始化方法, 现在很轻松的知道了,runstate为Running状态,workCount等于0。

进行总结

workerCount 线程池中当前活动的线程数量,占据ctl变量的低29位。

runState 是线程池运行状态,占据ctl变量的高3位。有5种状态:

  • RUNNING:接收新的任务,并且队列中的任务。
  • SHUTDOWN:不接收新的任务,但是会处理队列中的任务和正在运行中的任务。
  • STOP:不接收新的任务,也不处理队列中的任务,中断正在执行中的任务。
  • DITYING:所有任务都已经终止,workerCount等于0,将运行terminated()方法
  • TERMINATED:terminated()方法执行完毕。

我们还要明白线程池运行状态的转换

  • running 到shutdown的过程:显式调用shutdown()方法,或者在线程池中调用finalize方法。
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    /**
     * Invokes {@code shutdown} when this executor is no longer
     * referenced and it has no threads.
     */
    protected void finalize() {
        shutdown();
    }
  • shutdown到stop的过程:调用了shutdownNow()方法
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
  • shutdown到ditying的过程:线程池和任务队列都为空。
  • stop到ditying的过程:线程池为空。
  • terminated的过程: 调用terminated()方法。
    /**
     * Method invoked when the Executor has terminated.  Default
     * implementation does nothing. Note: To properly nest multiple
     * overridings, subclasses should generally invoke
     * {@code super.terminated} within this method.
     */
    protected void terminated() { }

尾言

大家好,我是cmazxiaoma(寓意是沉梦昂志的小马),希望和你们一起成长进步,感谢各位阅读本文章。

如果您对这篇文章有什么意见或者错误需要改进的地方,欢迎与我讨论。 如果您觉得还不错的话,希望你们可以点个赞。 希望我的文章对你能有所帮助。 有什么意见、见解或疑惑,欢迎留言讨论。

最后送上:心之所向,素履以往。生如逆旅,一苇以航。

saoqi.png

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏专知

关关的刷题日记93 – Leetcode 11. Container With Most Water

关关的刷题日记93 – Leetcode 11. Container With Most Water 题目 Given n non-negative integ...

37570
来自专栏desperate633

LeetCode 213. House Robber II题目分析代码方法二

在上次打劫完一条街道之后,窃贼又发现了一个新的可以打劫的地方,但这次所有的房子围成了一个圈,这就意味着第一间房子和最后一间房子是挨着的。每个房子都存放着特定金额...

8220
来自专栏算法修养

CSU 1808 地铁 (Dijkstra)

Description  Bobo 居住在大城市 ICPCCamp。 ICPCCamp 有 n 个地铁站,用 1,2,…,n 编号。 m 段双向的地铁线路连接...

33680
来自专栏web编程技术分享

来谈谈JAVA面向对象 - 鲁班即将五杀,大乔送他回家??

39190
来自专栏数据结构与算法

5970 格子游戏

题目描述 Description Alice和Bob玩了一个古老的游戏:首先画一个n * n的点阵(下图n = 3)   接着,他们两个轮流在相邻的点之间画上红...

36790
来自专栏ACM算法日常

唯快不破的01序列——位运算初识

众所周知,计算机的运算使用的就是二进制,它会把十进制的数转化为二进制,然后进行二进制运算,最后再转回十进制展现给我们。而位运算指的是:由于数字在计算机里...

15530
来自专栏大神带我来搬砖

锱铢必较:程序员生存指南——正则表达式中使用断言

18530
来自专栏老九学堂

【趣味】这么简单的代码这样用,你会玩吗?

编程太累了吧?Bug太多了吧?腰太酸了吧?快来看看吧!!老九君精心为各位喜欢挑战和刺激的小伙伴们准备了一些有趣而不乏烧脑的代码,让你换换脑,活动活动逻辑思维,换...

35350
来自专栏程序员与猫

使用抽象类和接口的优解

10110
来自专栏工科狗和生物喵

【我的漫漫跨考路】数据结构之线性表

正文之前 ? 昨天晚上阶段性的完成了一部分数学的复习(一元积分学终于搞定了,后面的貌似没这么难了),所以今天打算撸一撸代码,结合前几天写的链表实现线性存储,今天...

38560

扫码关注云+社区

领取腾讯云代金券