线程池execute执行流程源码分析

在分析源码之前需要先介绍一些基本概念:线程池的几种创建方法及线程池中一些重要变量,有基础的可以跳过,直接从第3步开始看源码分析。

1、线程池主要有四种创建方方法:

  Executor提供的三种静态方法:  

// 使用核心线程及同步队列,效率高,但是消耗CPU
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 使用指定线程数及链表队列,效率不太高,大量并发会造成内存溢出
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 使用单个线程及链表队列,效率最低,大量并发会造成内存溢出
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 另外一种是自定义线程池:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);

自定义线程池中7个参数的意思:

 corePoolSize:核心线程数

maximumPoolSize:最大线程数(非核心线程数 = 最大线程数 - 核心线程数;核心线程数不够用,队列也塞满任务后会创建非核心线程执行任务)

keepAliveTime:非核心线程停止工作后存活时间

unit:时间单位,配合keepAliveTime使用

workQueue:阻塞队列(常用队列有SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue)

threadFactory:线程工厂,可自定义线程(默认实现:Executors.defaultThreadFactory())

handler:拒绝策略(默认实现:AbortPolicy)

2、线程池中的一些重要方法及变量:

// 线程池对象,二进制全长32位,高3位记录线程池状态,低29位记录线程池中线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 常量29,方便后边做位运算
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池容量,也就是线程池中的最大线程数
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;

// 111 代表线程处于运行状态,可正常接收并执行任务
private static final int RUNNING    = -1 << COUNT_BITS;
// 000 代表线程处于SHUTDOWN状态,不接受新任务,但是会处理正在执行的任务及阻塞队列中的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001 代表线程处于停止状态,不接受新任务,停止正在执行的任务并且也不会执行阻塞队列中的任务
private static final int STOP       =  1 << COUNT_BITS;
// 010 代表线程处于即将销毁状态,这是SHUTDOWN及STOP向TEMMINATED过渡的一个中间状态
private static final int TIDYING    =  2 << COUNT_BITS;
// 011 代表线程终止状态,处于TIDYING状态的线程调用terminated方法变为TERMINATED
private static final int TERMINATED =  3 << COUNT_BITS;

// 获取线程池状态
private static int runStateOf(int c)     { return c & ~COUNT_MASK; }
// 获取正在工作的线程数
private static int workerCountOf(int c)  { return c & COUNT_MASK; }

OK,了解这些后,看源码就容易多了。

3、execute源码分析

public void execute(Runnable command) {
        // 健壮性判断,传递任务为空,直接返回空指针异常
        if (command == null)
            throw new NullPointerException();

        // 线程池对象的32位int值
        int c = ctl.get();

        // 1、如果工作线程数 < 核心线程数
        if (workerCountOf(c) < corePoolSize) {
            // 创建核心线程
            if (addWorker(command, true))
                return;

            // 创建核心线程失败的话,这里会重新获取线程池对象的32位int值,因为存在并发情况
            c = ctl.get();
        }

        // 2、先判断线程池是否处于运行状态,若处于运行状态,则将任务加入队列
        if (isRunning(c) && workQueue.offer(command)) {
            // 重新获取线程池对象的32位int值,还是因为并发存在,线程池状态及线程池中线程数量随时会有变动
            int recheck = ctl.get();

            // 若线程池非运行状态,直接将任务移除队列
            if (! isRunning(recheck) && remove(command))
                // 移除队列失败,直接执行拒绝策略
                reject(command);
            // 若线程池处于运行状态,但是线程池中没有工作线程了
            else if (workerCountOf(recheck) == 0)
                //这时候就创建一个空任务的非核心线程(用于执行第2步中加入的那个任务)
                addWorker(null, false);
        }
        // 3、创建一个非核心线程
        else if (!addWorker(command, false))
            //创建失败,执行拒绝策略
            reject(command);
    }

代码中已经做了详细注释,基本比较清晰了,这里再总结一下流程:

1、任务进来,先创建核心线程执行任务,若核心线程数已经达到了上线,执行第2步

2、将任务放入阻塞队列(中途由于并发原因,线程池状态随时可能会被改变,会做一些相应的处理,执行拒绝策略呀,或者创建空任务线程,执行队列中的任务)

3、若阻塞队列中也放满了,就创建非核心线程执行任务,若线程数也已经达到了上线,就执行拒绝策略。

workQueue.offer(command)方法是进行入队操作的,就是把任务添加到对应的队列中,不同队列(SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue)的入队方式有所不同,不是重点,这里就不展开讲了,有兴趣的可自行百度。

这三步中都有一个方法addWorker,下边来分析这个方法

4、addWorker源码分析:创建线程执行任务

private boolean addWorker(Runnable firstTask, boolean core) {
        // 标示位,用于从内部循环中直接跳转到这里
        retry:
        // 下边的两个死循环就是为了修改工作线程的数量。
        for (int c = ctl.get();;) {
            // 这个方法中判断是否 c >= SHUTDOWN,也就是判断线城池是不是非运行状态(运行转态是-1,SHUTDOWN是0)
            if (runStateAtLeast(c, SHUTDOWN)
                // 若线程池处于运行状态,可直接跳过,不会进入到此处。
                // 非SHUTDOWN转态,或添加任务非空,或任务队列为空
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                // 进入到此处的情况总结:
                   // 1、线程池处于STOP、TIDYING、TERMINATED状态。
                   // 2、线程池处于SHUTDOWN转态,此时传递进来的任务不为空
                      // (那么传递的任务为空时为什么不返回而是继续往下走去创建线程呢,上边我们在分析execute方法时,
                     // 第2步提到了任务加入队列后,若没有工作线程,则会创建一个空任务的线程来执行任务,
                     // 所以空任务就是这么由来的,这里不应该卡掉,需要创建一个空任务的线程去执行队列中的任务)
                  // 3、线程池处于SHUTDOWN转态,此时传递进来的任务为空,
                     // 但是任务队列中已经没有需要执行的任务了,就不需要走下边的方法去创建线程了。
                return false;

            for (;;) {
                // 这里区分要创建核心线程还是非核心线程,判断线程池总数量是否达到了指定上限
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                // 通过CAS方法修改工作线程数
                if (compareAndIncrementWorkerCount(c))
                    // 修改成功结束外层循环
                    break retry;

                c = ctl.get();  
                // 重新获取线程池状态后再次判断线程池是否非运行转态
                if (runStateAtLeast(c, SHUTDOWN))
                    // 若非运行状态直接跳到外层循环,继续以上步骤
                    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) {
                // worker继承了AQS,这里就是获取锁
                final ReentrantLock mainLock = this.mainLock;
                // 加锁,防止多线程同时修改workers及largestPoolSize的值
                mainLock.lock();
                try {
                    int c = ctl.get();

                    // 线程池处于运行状态
                    if (isRunning(c) ||
                        // 或线程池处于SHUTDOWN转态,切任务为空(这里以上有分析,不再解释)
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        // 判断一下线程是否已经启动(健壮性判断,我的线程刚创建还没调用start启动,错此时线程已经启动,直接报错)
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 将工作线程加入到集合中(用于统计数量)
                        workers.add(w);
                        // 工作线程数量
                        int s = workers.size();
                        // largestPoolSize记录的是最大的工作线程数,若此时的workers数量大于了这个值,就替换一下。
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 记录工作线程已经加入到结合中了
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                // 若工作线程已加入集合中
                if (workerAdded) {
                    // 启动线程
                    t.start();
                    // 记录线程已启动
                    workerStarted = true;
                }
            }
        } finally {
            // 若工作线程未启动,就执行加入队列失败方法
            if (! workerStarted)
                // 主要就是将worker从workers中移除,线程池中工作线程数减1,以及终止线程
                addWorkerFailed(w);
        }
        return workerStarted;
    }

以上就是我对线程池execute方法的源码分析,几乎每一行代码都添加了注释,可自行对照理解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值