线程池源码分析

参考文献:https://javadoop.com/post/java-thread-pool

一、线程池的使用

继承关系:Executor(接口)——>ExecutorService(接口)——>AbstractExecutorService——>ThreadPoolExecutor

  • Executor
    execute(Runnable runnable):最基本的方法,代表往线程池中提交任务。使用时一般用 submit() 代替,submit() 对 execute()进行了封装
  • ExecutorService
    • 任务提交(最常用):
      Future submit(Callable task)
      Future submit(Runnable task, T result);
      Future<?> submit(Runnable task);
    • 批量任务提交(可带超时时间):
      List<Future> invokeAll(Collection<? extends Callable> tasks)
      T invokeAny(Collection<? extends Callable> tasks)
    • 关闭线程池
      void shutdown();
      List shutdownNow();
      isShutdown();
      isTerminated();boolean awaitTermination(long timeout, TimeUnit unit):在规定时间内等待线程池Terminated
  • AbstractExecutorService
    AbstractExecutorService对ExecutorService定义的接口进行了初步实现:实现了submit()、invokeAll()、invokeAny()方法,但是方法内部调用的 execute() 方法还没有实现,而是在 ThreadPoolExecutor 进行具体实现
  • ThreadPoolExecutor
    ThreadPoolExecutor除了对上述接口定义的剩余方法进行了实现外(包括execute() 方法),还定义一些额外的方法:
    purge():尝试从工作队列中删除所有已取消的未来任务。
    getActiveCount():返回正在执行任务线程的大致数量
    getTaskCount():返回计划执行的任务的大致总数。
    getCompletedTaskCount():返回已完成执行的任务的大致总数。

二、线程池源码

1. execute() 方法

具体来看 ThreadPoolExecutor 对 execute() 方法是怎么实现的

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();

    // 1.如果当前线程数少于核心线程数,那么直接添加一个 worker 来执行任务,
    // 创建一个新的线程,并把当前任务 command 作为这个线程的第一个任务(firstTask)
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 到这里说明,要么当前线程数大于等于核心线程数,要么刚刚 addWorker 失败了

    // 如果线程池处于 RUNNING 状态,把这个任务添加到任务队列 workQueue 中
    if (isRunning(c) && workQueue.offer(command)) {
        /* 这里面说的是,如果任务进入了 workQueue,我们是否需要开启新的线程
         * 因为线程数在 [0, corePoolSize) 是无条件开启新的线程
         * 如果线程数已经大于等于 corePoolSize,那么将任务添加到队列中,然后进到这里
         */
        int recheck = ctl.get();
        // 如果线程池已不处于 RUNNING 状态,那么移除已经入队的这个任务,并且执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果线程池还是 RUNNING 的,并且线程数为 0,那么开启新的线程
        // 到这里,我们知道了,这块代码的真正意图是:担心任务提交到队列中了,但是线程都关闭了
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果 workQueue 队列满了,那么进入到这个分支
    // 以 maximumPoolSize 为界创建新的 worker,
    // 如果失败,说明当前线程数已经达到 maximumPoolSize,执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

总的来讲3种情况(考虑Running状态):

  1. 当前线程数 < corePoolSize:创建一个Worker,并把提交的任务作为该Worker的第一个任务,由Worker内的线程执行该任务
  2. 当前线程数 >= corePoolSize 或 添加Worker失败了(线程池被关闭了):尝试向任务队列中添加任务workQueue.offer(command)
  3. workQueue 队列满了:先尝试创建新的Worker,如果线程数已经达到了maximumPoolSize,则执行拒绝策略 reject(command)

可以看到,其实这就是线程池的基本原理

2. addWorker()方法

接下来来看addWorker()方法

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

        // 当状态大于 SHUTDOWN 时,不允许提交任务,且中断正在执行的任务
        // 但是如果线程池处于 SHUTDOWN,但是 firstTask 为 null,且 workQueue 非空,那么是允许创建 worker 的,因为需要创建新的 Worker把已经进入到 workQueue 的任务执行完
        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;
            // CAS成功,则跳出循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // CAS失败,说明有其他线程也在往线程池中添加线程
            // 由于有并发,重新再读取一下 ctl
            c = ctl.get();
            // 正常如果是 CAS 失败的话,进到下一个里层的for循环就可以了
            // 可是如果是因为其他线程的操作,导致线程池的状态发生了变更,如有其他线程关闭了这个线程池
            // 那么需要回到外层的for循环重新检查状态
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    /* 
     * 到这里,我们认为在当前这个时刻,可以开始创建线程来执行任务了,
     * 因为该校验的都校验了,至于以后会发生什么,那是以后的事,至少当前是满足条件的
     */

    // worker 是否已经启动
    boolean workerStarted = false;
    // 是否已将这个 worker 添加到 workers 这个 HashSet 中
    boolean workerAdded = false;
    Worker w = null;
    try {
        final ReentrantLock mainLock = this.mainLock;
        // 把 firstTask 传给 worker 的构造方法
        w = new Worker(firstTask);
        // 取 worker 中的线程对象,之前说了,Worker的构造方法会调用 ThreadFactory 来创建一个新的线程
        final Thread t = w.thread;
        if (t != null) {
            // 这个是整个线程池的全局锁,持有这个锁才能让下面的操作“顺理成章”,
            // 因为关闭一个线程池需要这个锁,至少我持有锁的期间,线程池不会被关闭
            mainLock.lock();
            try {

                int c = ctl.get();
                int rs = runStateOf(c);

                // 小于 SHUTTDOWN 那就是 RUNNING,这个自不必说,是最正常的情况
                // 如果等于 SHUTDOWN,前面说了,不接受新的任务,但是会继续执行等待队列中的任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // worker 里面的 thread 可不能是已经启动的
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
                    // 加到 workers 这个 HashSet 中
                    workers.add(w);
                    int s = workers.size();
                    // largestPoolSize 用于记录 workers 中的个数的最大值
                    // 因为 workers 是不断增加减少的,通过这个值可以知道线程池的大小曾经达到的最大值
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 添加成功的话,启动这个线程
            if (workerAdded) {
                // 启动线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 如果线程没有启动,需要做一些清理工作,如前面 workCount 加了 1,将其减掉
        if (! workerStarted)
            addWorkerFailed(w);
    }
    // 返回线程是否启动成功
    return workerStarted;
}

总的来讲,就以下3个步骤:

  1. 自旋CAS修改线程池的线程数标记
  2. 创建新 Worker,加锁地将该 Worker 存入线程池的 workers HashSet 中
  3. 启动 Worker中的线程执行任务

为什么第2步需要加锁:workers是 HashSet,非线程安全,通过加锁保证线程安全

3.runWorker()方法

上面启动了 Worker 内部的线程,线程启动后会去调用 Worker 的 run()方法

public void run() {
     runWorker(this);
}

具体来看 runWorker()方法:

// 此方法由 worker 线程启动后调用,这里用一个 while 循环来不断地从等待队列中获取任务并执行
// 前面说了,worker 在初始化的时候,可以指定 firstTask,那么第一个任务也就可以不需要从队列中获取
final void runWorker(Worker w) {
    
    Thread wt = Thread.currentThread();
    // 该线程的第一个任务(如果有的话)
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 循环调用 getTask 获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();          
            // 如果线程池状态大于等于 STOP,那么意味着该线程也要中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                // 这是一个钩子方法,留给需要的子类实现
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 到这里终于可以执行任务了
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    // 这里不允许抛出 Throwable,所以转换为 Error
                    thrown = x; throw new Error(x);
                } finally {
                    // 也是一个钩子方法,将 task 和异常作为参数,留给需要的子类实现
                    afterExecute(task, thrown);
                }
            } finally {
                // 置空 task,准备 getTask 获取下一个任务
                task = null;
                // 累加完成的任务数
                w.completedTasks++;
                // 释放掉 worker 的独占锁
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 如果到这里,需要执行线程关闭:
        // 1. 说明 getTask 返回 null,也就是说,队列中已经没有任务需要执行了,执行关闭
        // 2. 任务执行过程中发生了异常
        // 第一种情况,已经在代码处理了将 workCount 减 1,这个在 getTask 方法分析中会说
        // 第二种情况,workCount 没有进行处理,所以需要在 processWorkerExit 中处理
        // 限于篇幅,我不准备分析这个方法了,感兴趣的读者请自行分析源码
        processWorkerExit(w, completedAbruptly);
    }
}

总的来讲,分为以下几步:

  1. getTask()从任务队列中获取任务(如果有firstTask则不需要获取)
  2. 加锁
  3. beforeExecute任务执行前钩子
  4. 执行任务task.run()
  5. afterExecute任务执行后钩子

该任务执行完后进入下一次循环

任务执行时为什么要加锁???
源码的注释如下:Before running any task, the lock is acquired to prevent other pool interrupts while the task is executing, and then we ensure that unless pool is stopping, this thread does not have its interrupt set.在运行任何任务之前,获取锁以防止任务执行时其他池中断,然后我们确保除非池停止,否则该线程不会设置其中断。

线程池中有一个interruptIdleWorkers() ,该方法会中断线程池中的空闲线程(没有在执行任务而是阻塞在getTask方法中),可以看到该方法在中断线程前会先去尝试获取worker的lock,如果该worker正在执行任务那么就会被上锁,进而不会被该方法中断
在这里插入图片描述

4. getTask()方法

接着来看 Worker究竟是如何从任务队列中获取任务

线程的回收也是在该方法中完成的:如果从该方法返回了null,就会跳出上面的循环,进而关闭回收线程

// 此方法有三种可能:
// 1. 阻塞直到获取到任务返回。我们知道,默认 corePoolSize 之内的线程是不会被回收的,
//      它们会一直等待任务
// 2. 超时退出。keepAliveTime 起作用的时候,也就是如果这么多时间内都没有任务,那么应该执行关闭
// 3. 如果发生了以下条件,此方法必须返回 null:
//    - 池中有大于 maximumPoolSize 个 workers 存在(通过调用 setMaximumPoolSize 进行设置)
//    - 线程池处于 SHUTDOWN,而且 workQueue 是空的,前面说了,这种不再接受新的任务
//    - 线程池处于 STOP,不仅不接受新的线程,连 workQueue 中的线程也不再执行
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 两种可能
        // 1. rs == SHUTDOWN && workQueue.isEmpty()
        // 2. rs >= STOP
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // CAS 操作,减少工作线程数
            decrementWorkerCount();
            return null;
        }

        boolean timed;      // Are workers subject to culling?
        for (;;) {
            int wc = workerCountOf(c);
            // 允许核心线程数内的线程回收,或当前线程数超过了核心线程数,那么有可能发生超时关闭
            timed = allowCoreThreadTimeOut || wc > corePoolSize;

            // 这里 break,是为了不往下执行后一个 if (compareAndDecrementWorkerCount(c))
            // 两个 if 一起看:如果当前线程数 wc > maximumPoolSize,或者超时,都返回 null
            // 那这里的问题来了,wc > maximumPoolSize 的情况,为什么要返回 null?
            //    换句话说,返回 null 意味着关闭线程。
            // 那是因为有可能开发者调用了 setMaximumPoolSize() 将线程池的 maximumPoolSize 调小了,那么多余的 Worker 就需要被关闭
            if (wc <= maximumPoolSize && ! (timedOut && timed))
                break;
            if (compareAndDecrementWorkerCount(c))
                return null;
            c = ctl.get();  // Re-read ctl
            // compareAndDecrementWorkerCount(c) 失败,线程池中的线程数发生了改变
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
        // wc <= maximumPoolSize 同时没有超时
        try {
            // 到 workQueue 中获取任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            // 如果此 worker 发生了中断,采取的方案是重试
            // 解释下为什么会发生中断,这个读者要去看 setMaximumPoolSize 方法。

            // 如果开发者将 maximumPoolSize 调小了,导致其小于当前的 workers 数量,
            // 那么意味着超出的部分线程要被关闭。重新进入 for 循环,自然会有部分线程会返回 null
            timedOut = false;
        }
    }
}

这里的关键就是:

timed = allowCoreThreadTimeOut || wc > corePoolSize;

Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
  • 如果线程数超过了corePoolSize,则会调用阻塞队列的poll方法,如果超过了keepAliveTime都没有任务,则会获取到null,并将timedOut设为true,进而return null,回收当前线程
  • 如果线程数没有超过corePoolSize,则会调用阻塞队列的take方法,只要没获取到任务就一直等待,直到获取到任务,而不会被回收(除非allowCoreThreadTimeOut参数被修改)
5. processWorkerExit()

那么从getTask()方法返回null,又是如何具体回收线程的呢,具体来看processWorkerExit()方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

可以看到进入回收方法后,加锁地在 workers(一个存放Workers的HashSet)中移除该Worker,从而完成线程的回收

且如果是意外删除的(出现异常),则会重新创建一个线程进行补充

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值