线程池工作流程(二)

源码分析

入口

使用全参的构造函数创建线程池对象

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // 核心线程数
                4,
                // 最大线程数
                4,
                // 救急线程存活时间
                10,
                // 时间单位
                TimeUnit.SECONDS,
                // 阻塞队列的实现
                new ArrayBlockingQueue<>(20),
                // 线程工厂
                new ThreadFactory() {
                    private final AtomicInteger threadIndex = new AtomicInteger(0);

                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, String.format("zhima-thread-%d", threadIndex.getAndIncrement()));
                    }
                },
                // 拒绝策略
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 提交一个带返回值的任务
        Future<String> future = threadPoolExecutor.submit(() -> {
            System.out.println(String.format("当前执行线程[%s]", Thread.currentThread().getName()));
            TimeUnit.SECONDS.sleep(2);
            return "我是执行结果";
        });
        // 提交一个不需要返回值的任务
        threadPoolExecutor.execute(() -> System.out.println("Hello"));
        // 获取返回值
        System.out.println(future.get());
    }
}

构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    // 参数的合理性校验
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    // 将构造的时候传入的参数保存
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

到这里线程池对象就创建完毕了,完活

疑问

看到这里肯定有疑问了

1、怎么没有看见封装了线程的Worker对象的创建?

2、哪里调用start启动了线程?

没错!到这里都还没有真正启动线程,这也是它的高明之处

它把线程的初始化延迟到了提交任务的时候,是一个懒加载的机制,这也需要注意更多对于细节的把控

下面看提交任务的流程

AbstractExectuorService#submit

没错,ThreadPoolExecutor并没有实现submit,而是直接调用的父类方法

public Future<?> submit(Runnable task) {
    // 判空
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    // 调用子类复写的execute
    execute(ftask);
    return ftask;
}
AbstractExectuorService#newTaskFor
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

ThreadPoolExectuor#execute

public void execute(Runnable command) {
    // 命令不得为空
    if (command == null)
        throw new NullPointerException();
    /*
     * 分三个阶段处理
     * Proceed in 3 steps:
     * 1.如果当前活跃线程数比核心线程数少,则尝试根据给定任务开启一个新的线程
     * 通过调用addWorker来原子地检查运行状态和活跃线程数量
     * 如果没有核心线程空闲就返回false
     * 
     * 到这一步就说明核心线程已经满了,需要将任务放到阻塞队列中
     * 2.如果任务成功入队,需要再做一次检查
     * 是否需要添加一个救急线程(因为有可能在上一次检查之后死了一个)?
     * 并且确保线程池是运行状态
     * 
     *3.任务入队失败,就尝试添加救急线程(最多有maximumPoolSize-corePoolSize)
     * 失败就拒绝此次任务
     */
    int c = ctl.get();
    // 当前核心线程数比最大核心线程数小
    if (workerCountOf(c) < corePoolSize) {
        // 尝试创建worker成功,直接返回
        if (addWorker(command, true))
            return;
        // 更新线程池状态
        c = ctl.get();
    }
    // 是运行状态,并且将command加入任务队列成功
    if (isRunning(c) && workQueue.offer(command)) {
        // 再一次检查线程池状态
        int recheck = ctl.get();
        // 当前运行池状态不是RUNNING并且从任务队列中移除command成功
        if (! isRunning(recheck) && remove(command))
            // 执行拒绝策略
            reject(command);
        // 线程池状态是RUNNING,并且当前核心线程数是0
        // 线程池状态不是RUNNING,移除任务失败,并且当前核心线程数是0
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 线程池状态是RUNNING
    // 线程池状态是RUNNING,command加入任务队列失败
    else if (!addWorker(command, false))
        reject(command);
}

好,这个方法中涉及很多知识点

ctl
/**
 * 主要地线程池控制状态,它是是一个原子整数,包裹了两个概念,
 * 分别是核心线程数和线程池运行状态
 * 为了让两个概念放到一个整数中,限制工作线程数量最大为(2^29)-1 而不是(2^31)-1 (2 billion)
 * 如果后续有issue的话,将其改为AtomicLong即可,并且掩码也要改在这个需求出现以前,用int再好不过了
 * 
 * workerCount是不允许被暂停的核心线程的数量,这个值可能会短暂地和真实核心线程数不一致
 * 比如当线程工厂创建线程失败,或者说是线程仍然在执行剩下的任务在被真正销毁之前
 * 用户可见的线程池容量是用当前workerSets的大小
 *
 * runState提供了线程池重要的生命周期状态的信息
 * The runState provides the main lifecycle control, taking on values:
 *	 RUNNING:接收新任务并且不断处理队列中的任务
 *	 SHUTDOWN:不接受新任务了,但是处理队列中的任务
 *	 STOP:不接受新任务,并且不处理队列中已经接收的任务,打断正在执行的任务
 *	 TIDYING:所有的都被执行完了,核心线程数为0,状态过渡到TIDYING后会执行terminated钩子函数
 *	 TERMINATED:terminnated钩子函数执行完毕将状态过渡到TERMINATED
 * 
 * 这些状态的数值顺序很重要,可以用来比较,有一些比较的方法下面会提
 * 运行状态随着时间单调增长,但是不是一定要经过每一种状态,过渡状态如下
 * 
 * RUNNING -> SHUTDOWN
 *	  调用shutdown函数
 * (RUNNING or SHUTDOWN) -> STOP
 *	  调用shutdownNow()
 * SHUTDOWN -> TIDYING
 *	  当队列和线程池都空了后
 * STOP -> TIDYING
 *	  当线程池空了(因为STOP状态的时候,是不需要处理队列中的任务的)
 * TIDYING -> TERMINATED
 *	  当terminated()钩子函数执行完毕
 * 
 * 当线程调用awaitTermination()等待,当运行状态过渡到TERMINATED的时候会返回
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

总结一下就是以下几点

  • ctl中保存了线程池的两个重要的属性:核心线程数、线程池状态
  • 核心线程数最大是2 ^ 29 - 1,占了int的低29位
  • 线程池状态有五种,占了int的高3位
  • 线程池状态会根据一些方法调用而改变

线程池状态如下所示

在这里插入图片描述
在这里插入图片描述

取出对应状态的方法
// 返回c对应的运行状态
private static int runStateOf(int c)     { return c & ~COUNT_MASK; }
// 返回c对应的工作线程数量
private static int workerCountOf(int c)  { return c & COUNT_MASK; }
// 通过rs和wc构造一个新的ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
比较运行状态的方法
// 运行状态c是否小于s
private static boolean runStateLessThan(int c, int s) { return c < s; }
// 运行状态c是否大于大于s
private static boolean runStateAtLeast(int c, int s) { return c >= s; }
// 线程池是否正在运行
private static boolean isRunning(int c) { return c < SHUTDOWN; }
修改ctl的方法
// 尝试为workerCount自增1
private boolean compareAndIncrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect + 1); }
// 尝试为workerCount自减1
private boolean compareAndDecrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect - 1); }
// 为workerCount自减1(一定能成功)
private void decrementWorkerCount() { ctl.addAndGet(-1); }
addWorker
/**
 * 根据线程池状态检查和创建的时候指定的边界判断是否可以创建一个新的Worker(核心线程和救急线程)
 * 如果可以,worker的数量会作相应的调整,如果可能,一个新的worker被创建并且启动,运行初始化任务
 * 方法返回false如果线程池已经进入STOP状态或者符合关闭条件
 * 线程工厂创建线程失败也会返回false
 * 线程创建失败要么是因为线程工厂返回的是null,要么是因为在线程启动的时候抛出了异常
 * 
 * @param firstTask
 * 参数firstTask是新创建的线程要执行的第一个任务
 * 当出现比核心线程数少或者队列已经满了
 * Worker会被创建,并且指定初始化任务绕过排队
 * 初始化空闲线程通常使用prestartCoreThread创建或者是为了替换其他死掉的worker
 * 
 * @param core
 * 是否需要判断是否可以启动救急线程
 * 即如果是true的话就使用maximumPoolSize左边界,如果是false就使用corePoolSize做边界
 */
private boolean addWorker(Runnable firstTask, boolean core) {
    // 循环标记
    // 可以使用continue retry从这层循环开始继续执行
    // 一般用于内层循环中想要直接从外层循环重新执行的情况
    // 这个循环的主要目的就是根据当前线程池状态和边界去判断能不能增加一个worker,这里是先原子性修改一下worker的值,尚未真正创建worker
    retry:
    for (int c = ctl.get();;) {
        // 下面第一段if单独分析
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty()))
            return false;

        // 能走到这里,线程池状态只有两种可能
        // 一种是SHUTDOWN(是为了消费任务队列剩余任务所以要开个救急线程去干活,后面就不提这种情况了)
        // 一种是RUNNING,正常运行状态
        for (;;) {
	        // 下面第二段if单独分析
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            // 如果修改workerCount成功就跳出retry循环,开始执行创建Worker的逻辑
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // re-check 一下ctl
            c = ctl.get();
            // 如果是RUNNING就继续内层的循环
            // 否则就执行外层循环
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // 修改workerCount失败了,但是我还是RUNNING状态,就可以重新尝试
        }
    }

    // 这是真正开始创建worker的逻辑
    // worker是否开始干活的标记位
    boolean workerStarted = false;
    // worker是否被添加到workerSet的标记位
    boolean workerAdded = false;
    // worker对象
    Worker w = null;
    try {
        // 创建对象,内部使用线程工厂创建线程(尚未调用start!)
        w = new Worker(firstTask);
        // 拿到worker里面的thread
        final Thread t = w.thread;
        // 创建线程成功
        if (t != null) {
            // 拿到全局锁
            final ReentrantLock mainLock = this.mainLock;
            // 上锁
            mainLock.lock();
            try {
                // 再拿到锁的时候再一次检查线程池状态
                // 以防止在本线程拿到锁之前,有的线程已经关闭线程池了
                int c = ctl.get();
                // 线程池状态是RUNNING
              // 或者
                // 这个runStateLessThan(c,STOP)其实就是为了判断当前状态是不是SHUTDOWN
                // 因为短路运算符的特性,如果当前线程池状态已经是RUNNING那么就会产生短路不会进行下面的判断
                // runStateLessThan(c,STOP)只有当c是RUNNING或者SHUTDOWN的时候才是true,派出了RUNNING,那就只能是SHUTDOWN了,可以改写成runStateOf(c)==SHUTDOWN
                // 并且
                // firstTask是null,这就是提过两次的
                // SHUTDOWN状态的时候为了消费任务队列剩余任务所以要开个救急线程去干活
                // 还要判断这种情况是和获得锁的原因是一样的,为了防止在拿锁前有的线程调用了线程池的关闭方法
                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && firstTask == null)) {
                    // 线程状态有问题
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    // 将新创建的worker加入到workers中
                    workers.add(w);
                    // 将标志位置为true
                    workerAdded = true;
                    int s = workers.size();
                    // 记录线程池最大线程数,这个是提供给用户做统计的
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            // 如果被添加成功就启动线程,并且将线程启动成功标志位置为true
            if (workerAdded) {
                // 下面任务启动单独讲
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 线程启动失败
        if (! workerStarted)
            // 这里有三种情况
            // 第一种情况是线程都创建失败了更别说启动失败了
            // 这种情况下,worker其实是没有加入到workerSet中的
            // 第二种情况是线程创建成功了,但是启动失败了
            // 这个情况下,worker加入了workerSet,但是因为其启动失败了,不能占用核心线程数的位置
            // (因为workerSet的长度就是用户可见的核心线程数,这个最开始提到了)
            // 所以要将worker从workerSet中删除
            // 第三种情况是在上面拿到锁开始创建worker的时候已经有线程执行了线程池的关闭方法,就没有真正将新创建的worker加入到workers中,也就没有启动
            addWorkerFailed(w);
    }
    return workerStarted;
}
第一段if

这个if语句的段位很高,需要反复琢磨!

我在这段分析的下面写了改写版的if语句,超清晰,你可以选择先看改写版的再看源代码

        // 线程池状态是SHUTDOWN、STOP、TIDYING、TERMINATED 
        // 并且
        	// (如果线程池状态是SHUTDOWN,就需要进行后面的判断,如果状态是STOP、TIDYING、TERMINATED则直接短路,方法返回false)
	        // 状态是STOP、TIDYING、TERMINATED
        	// 或者
	        // (线程池状态肯定是SHUTDOWN,这个状态是不可以接收任务了)
        	// firstTask不为null
        	// 或者
        	// (线程池状态肯定是SHUTDOWN,这个状态下,是需要继续处理任务队列中的任务的,但是队列为空了就不需要处理任务了,方法返回false表示我已经把队列中的任务都完成了)
        	// 队列为空
        
        	// (那就很奇怪了,不管这个任务是不是null,都不需要处理了呀,这里可以假设一下这种情况
        	// 线程池状态是SHUTDOWN,firstTask是null,workQueue不空
        	// 这说明什么?说明在SHUTDOWN之后,所有核心线程都死了执行不了任务,但是任务队列中还有任务没有处理呢,为了保证对SHUTDOWN定义(不接受新任务,但是会把任务队列中的任务执行完),所以这里需要创建线程去将任务队列中的任务执行完
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty()))
            return false;
改写版
// 如果状态是STOP、TIDYING、TERMINATED,直接返回false,管他firstTask是什么,管他workQueue为不为空,我都不管了
if(runStateAtLeast(c, STOP)) {
    return false;
}
// 到了这里线程池状态只能是RUNNING、SHUTDOWN
// 所以这个if语句这么写也可以
// if(runStateAtLast(SHUTDOWN)) { }
if(runStateof(c) == SHUTDOWN) {
    // 不能接受新的任务了
    if(firstTask != null) {
        return false;
    }
    // 已经把任务队列中的任务都执行完了
    if(workQueue.isEmpty()) {
        return false;
    }
}

第二段if
// 好这个if段位非常高和刚刚开头的if不相上下
// 从里往外看首先看这个
// (core ? corePoolSize : maximumPoolSize)
// 这个core是方法参数,参数说明里面有,取一个值做边界的,下面这张图说明了为什么要和COUNT_MASK做与运算
// 所以这个if的作用就是判断当前workerCount是否大于等于边界,如果大于等于了,就不能创建新的worker了
if (workerCountOf(c)
    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
    return false;

在这里插入图片描述

任务启动

这里直接调用的Thread的start方法,所以需要知道构造的时候,runnable对象是谁

在这里插入图片描述

就是Worker对象,那么就在Worker对象中被复写的run方法就是这个线程启动的时候要执行的代码

Worker的run方法就一行代码

public void run() {
    runWorker(this);
}
runWorker
/**
	 * worker的事件循环,不断从任务队列中去拿任务,然后去执行它们
     * 1、有一个初始化任务,在这种情况下,我们不需要去从任务队列中拿任务,而是先执行
     * 这个初始化任务。否则就需要调用getTask不断地去取任务,如果getTask由于配置参
     * 数或者线程池状态返回null那么worker就退出循环。如果存在了异常,会被
     * completedAbruptly记录下来然后调用processWorkerExit去替换当前发生异常的线程
     * 
     * 2、在运行任务之前,要先拿锁,防止在执行任务的时候被打断。保证除非线程池stop了
     * 不然线程是不会被打断的
     *
     * 3、每一个任务在被执行之前都会调用beforeExecute,它可能会抛出异常,在这种情况下
     * 需要让线程死亡,不执行任务本身
     *
     * 4、假设beforeExecute正常执行完毕了,执行拿到的task,收集它抛出的所有异常,
     * 传给afterExecute,只分贝处理RuntimeException、Error和Throwables
     * 因为我们不能再run方法中重新抛出Throwables,所以在退出的时候把它们包装在errors中
     *(给线程的UncaugthExceptionHandler处理)任何异常都会导致线程死亡
     * 
     * 5、在task顺利执行后,调用afterExecute方法,也有可能会抛出异常,也会导致线程死亡
     *
     */
final void runWorker(Worker w) {
    // 拿到当前的线程
    Thread wt = Thread.currentThread();
    // 拿到worker的第一个线程
    Runnable task = w.firstTask;
    // 将firstTask置空以后就不用了
    w.firstTask = null;
    // 在w解锁后就可以打断了
    w.unlock();
    // 记录是否抛出了异常
    boolean completedAbruptly = true;
    try {
        // task不空或者通过getTask拿到的task不为null就一直循环
        while (task != null || (task = getTask()) != null) {
            // 上锁
            w.lock();
            	// 如果线程池进入了STOP状态,需要将线程中断
            	// 否则,确保线程不被中断
            	// 当前线程状态已经为STOP、TIDYING或者TERMINATED了
            	// 或者
            	// 当前线程已经被中断了(并且清除打断标记)并且线程状态已经为STOP、TIDYING或者TERMINATED了
            // 并且
            	// wt没有被中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                // 对worker中的线程打断
                wt.interrupt();
            try {
                // 执行beforeExecute
                beforeExecute(wt, task);
                try {
                    // 执行任务的run方法
                    task.run();
                    // 执行afterExecute,没有出现异常
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    // 执行afterExecute,出现了异常
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                // 将task置空,交给getTask去赋值
                task = null;
                // 完成任务数量加一
                w.completedTasks++;
                // 解锁
                w.unlock();
            }
        }
        // 记录是不是用户的run抛出的异常
        completedAbruptly = false;
    } finally {
        // 退出循环的时候执行processWorkerExit,回收worker
        processWorkerExit(w, completedAbruptly);
    }
}
getTask
/**
 * 阻塞或者限时等待拿任务,依赖用户的配置
 * 如果worker必须返回退出则返回null,有以下几种原因:
 * 1、线程数超过了maximumPoolSize
 * 2、线程池状态已经是STOP了
 * 3、线程池状态为SHUTDOWN,并且任务队列为空
 * 4、阻塞获取任务超时,也就是worker空闲时间超时,则根据用户配置判断是否需要返回null
 * 但是如果任务队列中有任务,并且线程池中只有当前一个线程了,那就不能返回null
 * 
 * @return 返回null就说明当前这个worker要被销毁了
 */
private Runnable getTask() {
    // 记录是否超时
    boolean timedOut = false;

    for (;;) {
        // 拿到线程池状态
        int c = ctl.get();

        // Check if queue empty only if necessary.
        // 运行状态是SHUTDOWN STOP TIDYING TERMINATED
        // 并且
        // 运行状态是STOP TIDYING TERMINATED或者队列为空
        // 这个if等同于
        // if(runStateAtLeast(c,STOP) || (runStateOf(c) == SHUTDOWN && workQueue.isEmpty())){...}
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            // 将worker数量减一
            decrementWorkerCount();
            // 表示销毁当前的worker
            return null;
        }

        // 拿到当前工作线程的数量
        int wc = workerCountOf(c);

        // 是否需要在超时的时候销毁
        // 1、允许核心线程销毁
        // 2、当前线程数比核心线程数大(这些线程是救急线程)
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            // 当前线程数比最大线程数都大了
            // 或者
            // 超时了并且需要在超时的时候销毁线程
        // 并且
        	//当前线程数比1大,或者队列为空
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // 尝试将worker数量减一,失败则循环重试
            if (compareAndDecrementWorkerCount(c))
                // 成功则返回null
                return null;
            continue;
        }

        try {
            // 去任务队列中去取任务
            // 根据是否需要超时等待选择不同的阻塞方法
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 如果取到了任务就直接返回
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
reject

在这里插入图片描述

这里的handler就是创建ThreadPoolExecutor的时候指定的拒绝策略

这里就讲一种内置的CallerRunsPolicy拒绝策略的执行过程

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 如果线程池还没有进入SHUTDOWN状态,则直接调用r.run()
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

这里肯定有点疑问?

为啥直接r.run()就是提交任务的线程去执行任务

因为execute/submit是提交任务的线程去做的,走到了reject的时候还是当前提交任务的线程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芝麻\n

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值