线程池ThreadPoolExecutor核心方法execute()原理源码分析

线程池ThreadPoolExecutor核心方法execute()原理源码分析

首先了解个基本知识:线程只能启动一次,重复启动会报异常,下面会用到这个知识点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tttaXJJ9-1611288459213)(/Users/hhh/Library/Application Support/typora-user-images/image-20210107172439162.png)]

注:为了显示行号,所以一下源码均以图片展示

1. 构造方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kpc2nFsO-1611288417524)(/Users/hhh/Library/Application Support/typora-user-images/image-20210119165734675.png)]

首先解释一下各个参数:

①corePoolSize:核心线程数

②maximumPoolSize:最大线程数(最大线程数只限制了下限,没有限制上限,但是上限也有限制并不能达到Integer.MAX_VALUE,下文分析),通过该下限限制可以发现最大线程最少有1个

③keepAliveTime:当前线程数>核心线程数时,允许超过核心线程数的这些线程等待任务的最大时间

④unit:③的时间单位

⑤workQueue:用来存放任务的队列

⑥threadFactory:用来创建线程池中线程的工厂类(默认使用的默认提供的共厂类,通过源码可以了解到该工厂创建的线程是非守护线程并且优先级为5)

⑦handler:拒绝服务的对象,提供拒绝服务功能

2.execute()方法分析前提知识

①首先要知道线程池中有线程池的自身状态,以及线程池中运行线程的个数,程池很巧妙的将这两个信息保存在一个32bit的int类型变量中,并且这个变量 是线程安全的AtomicInteger对象,其中使用高3位来保存线程池的自身状态,低29位保存线程池中工作线程的个数。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cy2T1hrx-1611288417525)(/Users/hhh/Library/Application Support/typora-user-images/image-20210119172806822.png)]

②线程池通过一系列的位运算来计算线程池的状态以及工作线程数,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XnZDTqc0-1611288417527)(/Users/hhh/Library/Application Support/typora-user-images/image-20210119173204315.png)]

其中CAPACITY为(1<<29)-1正好是高3位全是0,低29位全是1。这样就可以通过AtomicInteger对象中的值与CAPACITY进行相应的位运算来计算线程池状态以及当前工作线程的个数

③线程池的自身状态从运行到关闭也有个大小的顺序关系(因为线程池状态是保存在int的高3位中,可以有大小关系COUNT_BITS为29),如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQ39cExt-1611288417528)(/Users/hhh/Library/Application Support/typora-user-images/image-20210119173805175.png)]

3.execute(Runnable command)方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yh75KO2-1611288417530)(/Users/hhh/Library/Application Support/typora-user-images/image-20210119174328127.png)]

从源码可以看出,该方法主要分为三步分别如下:

首先获取记录线程池状态以及工作线程数的int变量值c

第一步:根据c计算出工作线程数,如果线程数<核心线程数,创建一个核心线程并立刻处理该次提交的任务,然后该方法结束。

第二步:如果第一步条件不成立(工作线程>=核心线程||创建核心线程因为某种原因失败),判断线程池是否是运行状态,如果是运行状态然后把该次提交的任务放在任务队列中_延时处理_,然后再重新获取这个c再检查一次线程池的状态是否是运行状态,如果不是运行状态就把刚才添加的任务从任务队列中移除,把这个移除的任务交给构造方法传入的拒绝服务对象,走拒绝服务逻辑。如果是运行状态然后检查线程池是否还有工作线程数,如果没有则创建一个线程保证,至少有一个线程消费该任务队列。

第三步:如果前两步不成立(线程池不是运行状态||任务队列添加满了),然后添加一个非核心线程立刻处理该次提交的任务(早于大多数还在队列中等待的任务),如果添加失败,则会把该次任务交给拒绝服务对象,走拒绝服务逻辑。

总结:如果核心线程<用户输入的核心线程数则添加核心线程,并用该线程处理该任务,否则把该任务添加到任务队列中等待线程处理,如果核心线程队列满了,则添加非核心线程,并处理该次提交的任务,如果都不满足,则拒绝服务该任务。

4.addWork(Runnable firstTask, boolean core)方法

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
          	//①对线程池状态进行检测
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

          	//②对工作线程数进行检测
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

  			//③添加工作线程
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

该方法主要就是添加工作线程,并启动该线程,其他核心处理逻辑主要在线程的run()方法中,首先分析该方法,在我看来该方法主要完成了两个功能,一个是添加工作线程并启动该线程,另一个是功能是在添加线程前对线程池状态以及工作线程数所做的检查。

①首先分析前置检查,该方法也是进来先获取那个变量c,通过位运算获取线程池的状态,先对线程池的状态进行检测(上文源码我标注了),这里判断逻辑有点饶人,我只说添加成功的两种情况:a.如果此时线程池是运行状态了,则检查通过继续向下判断。b.如果此时线程池是SHUTDOWN状态(SHUTDOWN状态与STOP状态的区别:处于SHUTDOWN状态时继续处理队列中的任务,但是不接受新任务了。处于STOP状态时既不处理队列中的任务,也不接受新任务),并且此次添加线程时没有携带新任务,并且队列中不是空的还有任务需要处理,则检查通过继续向下判断。

②经过①对线程池状态的检查通过后,开始检查线程池中工作线程的个数,通过位运算获取工作线程数,进行判断,如果线程数>=CAPACITY(该值为(2<<29)-1,这就是我上文提到的,为什么线程池的最大线程数不能达到Integer.MAX_VALUE),或者根据本次创建的是核心线程数还是非核心线程来判断是否达到上限,如果达到了则添加失败返回,如果没达到,则通过CAS对变量c+1;检查到此结束,开始走添加线程逻辑。

③构建Worker对象(Worker继承了AQS并实现了Runnable,所以他既是一个锁也是一个线程),程序很长其实逻辑很简单就是创建了一个worker对象,如果此次添加线程携带任务,并把任务封装到Worker对象中,在Worker启动时先处理该任务再处理任务队列中的任务(下文分析),并把该对象保存在一个set集合,并启动该worker。

5.处理任务的核心逻辑Worker.run()方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVtOKrZE-1611288417531)(/Users/hhh/Library/Application Support/typora-user-images/image-20210121174506955.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i6vYvlyP-1611288417532)(/Users/hhh/Library/Application Support/typora-user-images/image-20210121174531000.png)]

该方法主要有两步组成:①如果该Worker初始时有需要处理的任务,先处理,处理完了从任务队列中获取,如果没有初始任务直接从队列中获取并处理,是一个循环。②任务队列中的任务处理完,线程结束,进行后续处理(开始的时候提到过,一个线程不能重复启动)。

①在第1134行进行获取任务,并在1149行进行处理,处理前后分别有钩子函数供程序员做额外操作,其中getTask()是从队列中获取任务的核心逻辑,下文展开分析。

②1167行对结束的线程做后续处理,下文展开分析。

6.getTask()方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFkYdv99-1611288417533)(/Users/hhh/Library/Application Support/typora-user-images/image-20210121182554346.png)]

该方法主要有三步:①判断线程池状态。②如果在①中没有返回那么需要对线程退出条件进行检查。③如果在②中没有返回则去任务队列中拿任务进行处理。

如果线程池状态>=STOP状态,此时为那么任务队列中的任务也就不处理了;或者线程池状态>=SHUTDOWN并且任务队列中已经没有任务了,那么对变量c-1并且接返回null,说明该线程可以走结束的后续逻辑了

②退出条件:第1062-1069行综合来说两种情况: a.如果工作线程数>最大线程数并且工作队列是空的;b.如果核心线程被设置为允许超时,或者工作线程中有非核心线程,并且这些非核心线程在拿任务的时候超过时,并且此时任务队列是空的。如果以上两种情况符合某一个则达到退出条件则对变量c-1并返回null,说明该线程可以走结束的后续逻辑了

如果能立刻拿到就返回,否则根据配置是否允许核心线程超时以及工作线程是否>核心线程数来定时等待,或者阻塞等待

7.processWorkerExit(Worker w, boolean completedAbruptly)方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3KfN9v5u-1611288417534)(/Users/hhh/Library/Application Support/typora-user-images/image-20210122114421468.png)]

该方法主要完成了三件事:①把该线程(马上要结束了既马上走完run方法了)从保存线程的set集合中删除,②根据任务队列以及工作线程数来修改线程池的一些状态,③一般情况下会走这一步,如果此时线程池处于RUNNING或者SHUTDOWN状态,并且此时的工作线程数<核心线程数则创建一个非核心线程走addWorker()方法,如果设置核心线程允许超时,则保证线程池中最少有一个线程来服务(处理任务队列中的任务)。

至此,以上讲解完全个人理解,可能某些地方存在出入希望大佬指正。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页