ThreadPoolExecutor线程池内部工作原理及源码解析(下)

本文将深入探讨ThreadPoolExecutor线程池的工作原理和源码实现。通过了解其内部机制,我们可以更好地利用线程池来提高程序的性能和并发能力。

回顾

程池解决的问题:减少线程创建销毁的开销,重复利用。便于监控管理。

线程池的核心参数:

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数 == 核心线程数 + 非核心线程数
  • keepAliveTime:最大空闲时间,默认是非核心线程的
  • unit:时间单位
  • workQueue:存储任务的
  • threadFactory:构建thread对象的
  • handler:拒绝策略

线程池的核心属性:int类型的属性ctl,维护线程池状态和工作线程数

线程池的状态:

  • RUNNING:可以接收新任务,工作队列中排队的任务正常处理
  • SHUTDOWN:不能接收新任务,工作队列中排队的任务正常处理
  • STOP:不能接收新任务,工作队列中的任务会被移除作为返回结果

线程池的执行流程:


一、addWorker源码流程

在线程池构建工作线程时:

  • 判断:
    • 判断线程池状态
    • 判断工作线程个数
  • 创建并启动
    • 构建工作线程
    • 启动工作线程
// 创建工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
    // ===================判断状态,工作线程个数=====================
    retry:
    for (;;) {
        // 拿到核心属性ctl
        int c = ctl.get();
        // 从ctl中获取线程池状态
        int rs = runStateOf(c);

        // rs >= SHUTDOWN:满足这个判断,代表线程池状态不是RUNNING
        if (rs >= SHUTDOWN &&
            // 状态是SHUTDOWN,没传递任务,同时工作队列中有任务
            // 如果满足这个情况,不进入if。
            ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;

        for (;;) {
            // 从ctl中获取工作线程个数
            int wc = workerCountOf(c);
            // wc >= CAPACITY:健壮性判断,工作线程个数,不能超过(1<<29) - 1
            if (wc >= CAPACITY ||
                // 判断工作线程是否满足核心参数的设置。
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 可以尝试创建工作线程。
            // 利用CAS的原子性,将ctl + 1,如果成功,直接跳出外层循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 有并发情况,修改ctl失败。
            // 重新获取ctl
            c = ctl.get(); 
            // 基于重新获取到的ctl拿到线程池状态,比对一下和之前状态是否一致
            if (runStateOf(c) != rs)
                // 说明线程池状态改变了。重走外层for循环
                continue retry;
        }
    }

    // ===================创建工作线程,启动工作线程=====================
    // ===================创建工作线程,本质就是new一个Worker对象,扔到HashSet里=====================
    // 工作线程启动了咩:没~
    // 工作线程创建了咩:没~
    boolean workerStarted = false;
    boolean workerAdded = false;
    // Worker就是工作线程
    Worker w = null;
    try {
        // new工作线程!同时也构建了thread对象,传入了任务
        w = new Worker(firstTask);
        // 拿到Worker对象中声明好的thread对象
        final Thread t = w.thread;
        // 健壮性判断
        if (t != null) {
            // 获取到线程池里面的锁
            final ReentrantLock mainLock = this.mainLock;
            // 加锁
            mainLock.lock();
            try {
                // 线程池状态
                int rs = runStateOf(ctl.get());
                // 再次判断线程池状态能否构建。
                if (rs == RUNNING ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 如果线程已经start了,那就扔异常!
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 将工作线程添加到HashSet集合中
                    workers.add(w);
                    // 获取工作线程个数
                    int s = workers.size();
                    // largestPoolSize:记录工作线程的历史最大值。
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // 工作线程创建成功。
                    workerAdded = true;
                }
            } finally {
                // 释放锁资源
                mainLock.unlock();
            }
            // 如果创建工作线程成功
            if (workerAdded) {
                // 启动工作线程
                t.start();
                // 启动成功!
                workerStarted = true;
            }
        }
    } 
    return workerStarted;
}

Worker(Runnable firstTask) {
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

thread.start();
thread.start();

二、Worker对象的封装

addWorker里,线程启动后,执行了t.start();

线程启动要执行run方法,run方法干嘛了???

发现线程启动后,执行的是Worker中的run方法。

Worker中的run方法,执行了runWorker方法。

// 简略版的Worker,核心内容都在。
private final class Worker implements Runnable {

    final Thread thread = new Thread(this);
  
    Runnable firstTask;


    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    // thread.start后,执行的是这个run方法。
    public void run() {
        // 想看工作线程的后续处理,要看runWorker方法了
        runWorker(this);
    }
    // 省略部分代码
}

三、runWorker执行任务流程

在runWorker方法中,获取Worker对象自带的任务,如果任务不为null,执行当前任务,还提供了前后两个勾子函数,供咱们程序员自行实现。任务执行完,将引用置位null,同时记录任务完成数 + 1

// 线程启动后,执行的runWorker方法
// 省略了部分中断内容
final void runWorker(Worker w) {
    // 拿到当前工作线程
    Thread wt = Thread.currentThread();、
    // 拿到new Worker时,传递过来的任务
    Runnable task = w.firstTask;
    // help gc
    w.firstTask = null;
    try {
        // 拿到的任务,不为null,直接执行
        // 如果拿到的任务已经处理完了,就通过getTask拿。
        while (task != null || (task = getTask()) != null) {
            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) {
                    thrown = x; throw new Error(x);
                } finally {
                    // 执行任务后,有一个钩子函数。给程序员们,留的扩展的口子。
                    afterExecute(task, thrown);
                }
            } finally {
                // 将task置位null
                task = null;
                // 当前线程处理任务个数 + 1
                w.completedTasks++;
            }
        }
    }
}

四、getTask获取任务流程

工作线程执行完一个任务后,会执行getTask方法。

getTask方法会再次获取一个任务,然后执行任务。

getTask其实就是去工作队列中拉取任务。

核心线程基本就是通过take拉取任务,没任务就死等。

非核心线程就是基于poll拉取任务,等待keepAliveTime时间,没任务就告辞。

// 工作线程来收任务啦!!
private Runnable getTask() {
    // 超时了没???没呢!
    boolean timedOut = false; 
    // 死循环~~~
    for (;;) {
        // 再次获取ctl,拿到线程池状态
        int c = ctl.get();
        int rs = runStateOf(c);

        // 线程池状态为STOP,直接进if
        // 线程池状态为SHUTDOWN,并且工作队列为空,直接进入if
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 进来代表当前线程可以凉凉了!
            // 对ctl - 1(保证原子性。)
            decrementWorkerCount();
            // 返回null
            return null;
        }

        // 拿到工作线程个数
        int wc = workerCountOf(c);

        // 如果有非核心线程,timed为true。(不考虑核心线程超时问题。)
        boolean timed = wc > corePoolSize;

        // wc > maximumPoolSize正常人类的操作不会发生。
        // 第一次,timedOut肯定是false。
        if ( (timed && timedOut) 
            && (wc > 1 || workQueue.isEmpty())) {
            // 第一次进不来!
            // 到这,也没必要去工作队列拉取任务了。
            // 对ctl - 1,当前线程可以拜拜了!
            if (compareAndDecrementWorkerCount(c))
                return null;
            // 如果CAS失败了,重新走状态判断,和工作线程数判断
            continue;
        }

        try {
            // 如果有非核心线程,
            // 执行工作队列的poll方法,将线程挂起keepAliveTime时间,时间到了,没任务,获取到null
            // 如果timed为false,核心线程直接take在工作队列的位置,死等任务,没任务就挂起等着,一直等到有任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 如果拿到任务,直接返回任务
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

 五、shutdown关闭线程池流程

线程池使用完毕后,一定要手动的关闭线程池,不然工作线程无法被回收。

工作线程无法被回收,会导致Worker对象不会被回收。

Worker对象是线程池的内部类,内部类无法回收,外部类自然无法被回收。

线程池不用了之后,记得执行shutdown,shutdown内部会将线程池的状态修改为SHUTDOWN状态。

同时shutdown方法会中断工作线程

线程池内部多个地方都判断了线程池状态

  • 获取任务的getTask方法会返回null。
  • 这样runWorker方法中的while循环会退出。
  • while循环退出,runWorker方法结束
  • runWorker方法结束,run方法结束
  • run方法结束,工作线程凉凉~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值