【多线程】线程池源码(一)

上一篇文章讲了有关线程池的一些简单的用法,这篇文章主要是从源码的角度进一步带你们了解线程池的工做流程和工做原理。java

首先先来回顾下如何使用线程池开启线程多线程

private static void createThreadByThreadPoolExecutor() {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        executor.execute(myThread);
    }

能够看到其实没有其它特殊的地方,除了构建线程池的代码,其它最终要的就是executor.execute(myThread) 行代码了。函数

准备工做

在多线程系列的第一篇文章中提到了线程和进程的状态,线程池一样也有状态,以下:spa

  • Running: 容许接受新的任务,而且处理队列中的任务
  • Shutdown: 不接受新的任务,可是仍然会处理队列中的任务
  • Stop: 不接受新的任务,不处理队列中的任务,并且中端在运行的任务
  • Tidying: 全部的任务都已经终端,而且工做线程数归0,该状态下的线程都会调用terminated() 函数
  • Terminated:terminated() 函数调用完后就进入了此状态

首先须要知道4个概念WorkerworkersworkQueuetask.线程

  • 在线程池中有个比较重要的类,那就是Worker ,能够看到其实现了Runnable接口(其实就是工做线程),继承了AbstractQueuedSynchronizer类(俗称AQS,在多线程中是很重要的类)code

  • workers 就是Worker 的一个集合,private final HashSet<Worker> workers = new HashSet<Worker>();
  • task :须要执行的任务,也就是execute() 中的参数,实现了Runnable接口
  • workQueue 就是工做队列,就是上一篇文章中线程池构造函数中的工做队列,里面存储的就是须要执行的任务,队列是实现BlockingQueue接口的类,有如下这些实现对象

execute()

本方法传进去的类是须要实现Runnable接口的,做为一个command 传进去继承

遇到新的任务接口

  1. 若是工做线程数 < 核心线程数,那么直接加1个worker
  2. 若是线程池是正常的工做状态,而且工做队列可以添加任务,此时须要第二轮判断队列

    1. 若是线程池由于某种缘由不正常了,而且可以成功从工做队列中删除任务,那么直接采起拒绝策略
    2. 若是此时工做线程数为0,此时须要新建一个线程(而且这里建立的是非核心线程)来执行这个任务,为何是null呢,由于已经把任务放在工做队列里面了。若是新建的worker的firstTask是该任务的话,就会重复执行两次任务。
    3. 其它状况也就是正常状况下,是啥都不干,由于主要目的是把任务放到工做队列中
  3. 若是工做队列已经满了,则须要判断是否可以成功添加一个非核心线程,若是连非核心线程数都知足不了了,就是线程池真的搞不了了,累了,因此也会调用拒绝策略。
注意:核心线程和非核心线程只是语义上的说法, 没有本质上的区别

addworker()

addworker 的做用是检查是否能够根据当前池状态和给定界限(核心或最大值)添加新线程 ,而且经过第二个参数来断定是否建立核心线程,当为true的时候就是核心线程,反之就是非核心线程。源码里的注释以下

来看看代码具体是如何的

  1. 一进来就是一个死循环,这个死循环最主要的目的是确认线程池状态是否正常。若是线程池的状态大于SHUTDOWN,也就是处于STOP、TIDYING或者TERMINATED的时候,线程池都没了,还建立worker干啥,直接返回fasle;当线程池处于SHUTDOWN的时候,又得再次判断:

    1. 传进来的任务若是不为空,那么就不用添加worker的,银行下班了,办理完如今的顾客就不在办理了,否则一直来那不是一直不能下班。
    2. 传进来的任务为空,可是工做队列为空,一样不用添加worker,银行都没有任务了,并且又到点下班了。除了以上3中状况返回false,其它状况不作任何反应,往下走。
  2. 又是一个死循环,首先获得工做线程数若是超过了边界,好比超过了容量、核心线程数或者最大线程数,就不用添加worker了,银行实在是办理不了新的顾客了;当工做线程数正常的状况下,经过CAS来增长工做线程数,若是能增长成功就退出最外层循环。若是增长工做线程失败,那就是其它线程增长了该数量,若是此时线程池的运行状态发生了改变,则重复外层循环,不然就自旋直到成功增长工做线程数。
  3. 到这里就能够说基本扫除了障碍,以传进来的firstTask新建一个Worker,而后获取Worker里的线程。若是线程为null,那么就直接执行添加Worker失败的逻辑,不然就是正常的逻辑。
  4. 先来看正常的逻辑,拿到锁,而且开始又一次的获取线程池的状态

    1. 若是线程是正常运行状态,或者说是关闭状态下firstTask仍然为null,此时若是线程是alive 那么而说明线程已经开启,直接抛出异常。
    2. 正常状况下是wokers集合中添加新的worker元素,而且调整线程池最大值,设置workerAdded标志为true。
    3. 重点 ,当workerAdded为true的时候,开启工做线程,也就是代码中的t.start()
  5. 再来看失败的逻辑 ,是在第3点构造一个Worker对象,获取线程的时候,有可能获取到的线程为空,这样其实就是增长worker失败,返回false,在返回false以前会触发执行失败的逻辑addWorkerFailed() 逻辑。整个的逻辑是比较简单的,就再也不花费篇幅去阐述。

到这里,整个addWorker()的流程是比较清晰的,值得一提的就是第2行代码中的retry 这个看起来是关键字,但其实不是,仅仅只是一个相似标志位的东西,能够是retry,也能够是abc。

一般是配合for循环来使用,搭配continue和break能够达到goto的效果。好比continue retry; 就是跳到一开始最外层的for循环,break retry; 至关于退出整个循环。写一个最简单的例子你们都能知道了。

public class RetryExample {
    public static void main(String[] args) {
        abc:
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                if (j == 2) {
                    continue abc;
                }
                if (i == 8) {
                    break abc;
                }
                System.out.println("i: " + i + ", j: " + j);
            }
        }
    }
}

输出结果以下图所示

创做不易,若是对你有帮助,欢迎点赞,收藏和分享啦!

下面是我的公众号,有兴趣的能够关注一下,说不定就是你的宝藏公众号哦,基本2,3天1更技术文章!!!

相关文章
相关标签/搜索