Java 线程池原理分析

博客文章地址:Java 线程池原理分析

个人博客主页:www.samsa-blog.top 欢迎各位掘友交流

引用1:tech.meituan.com/2020/04/02/... 作者: 致远 陆晨

引用2:《Java 并发编程之美》 作者: 霍陆续 薛宾田

引用3:www.cnblogs.com/HuiShouGuoQ... 作者: 小窝蜗

关于为什么使用线程池,以及它的好处,优势就不做过多介绍了。

​ 核心类ThreadPoolExecutor继承了AbtractExecutorService,再往上,顶层抽象接口线程池状态和提交任务方法 ExecutorService,Executor。

​ Worker 继承 AQS Runnable 接口是具体承载任务的对象。

​ DefaultThreadFactory 线程工厂。

​ Executors 其实是个工具类里面提供了一些静态方法,这些方法根据用选择返回不同的线程池类型。

​ 线程池在内部实际上构建了一个生产者------消费者模型 ,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转。

其运行机制如下图所示:

一、线程池信息介绍

1.1 线程池状态

java 复制代码
// (高3位)用来表示线程池状态, (低29位)用来表示线程个数
// 默认是RUNNING状态, 线程个数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// 获取高3位(运行状态)
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 获取低29位(线程个数)
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 计算ctl新值(线斗呈状态与线程个数
private static int ctlOf(int rs, int wc) { return rs | wc; }

线程池状态含义如下:

  • RUNNING:接受新任务并且处理阻塞队列里的任务;
  • SHUTDOWN:拒绝新任务但是处理阻队列里的任务;
  • STOP:拒绝新任务并且抛弃阻塞队列的任务,同时会中断正在处理的任务;
  • TIDYING:所有任务都执行完(包含阻塞队列里面的任务)后当前线程池活动线程数为0,将要调用 terminated 方法;
  • TERMINATED:终止状态 terminated 方法调用完成以后的状态。

线程池状态转换图:

1.2 线程池参数

  • corePoolSize:线程池核心线程数;

  • workQueue:用于保存待执行的任务的阻塞队列;

  • maximunPoolSize:线程池最大线程数量;

  • ThreadFactory:创建线程的线程工厂;

  • RejectedExecutionHandler :拒绝策略;当队列满并且线程个数达到maximunPoolSize后采取策略。

  • keeyAliveTime :存活时间;如果当前线程池中的线程数量比核心线程数多,并且是闲置状态,则这些闲置的线程能存活的最大时间为keeyAliveTime;

  • TimeUnit:存活时间的时间单位。

1.3 线程池类型

在Java中,ExecutorService接口提供了多种线程池类型,可以根据不同的场景选择适合的线程池类型。以下是常见的几种线程池类型及其适用场景:

  1. FixedThreadPool(固定大小线程池):

    • 适用于执行长期任务,控制线程的最大并发数,可以保证线程池中的线程数量始终不变。
    • 适用于服务器端需要控制并发线程数量的场景,如:Web服务器。
  2. CachedThreadPool(缓存线程池):

    • 适用于执行大量短期异步任务的场景,线程池根据需要自动创建新线程,没有任务时会回收空闲线程。
    • 适用于执行很多短期异步任务的小程序或者负载较轻的服务器。
  3. SingleThreadExecutor(单线程线程池):

    • 只会创建一个单线程来执行任务,保证所有任务按顺序执行。
    • 适用于需要保证任务按照顺序执行的场景,如:事件循环、数据库连接池等。
  4. ScheduledThreadPool(定时任务线程池):

    • 适用于需要定时执行任务或延迟执行任务的场景。
    • 可以指定线程池大小,可以执行定时任务和周期性任务。
  5. WorkStealingPool(工作窃取线程池):

    • Java 8引入的一种线程池,基于ForkJoinPool实现,用于执行耗时较长的任务。

    • 可以充分利用多核处理器的优势,提高任务执行效率。

  • newSingleThreadExecutor:

    创建一个核心线程个数和最大线程都为1的线程池并且阻塞队列长度为Integer.MAX_VALUE

    keepAliveTime=0 说明只要线程个数比核心线程个数多并且当前空闲回收。

    java 复制代码
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
  • newFixedThreadPool:

    创建一个核心线程个数和最大线程个数都为nThreads的线程池,并且阻塞队列长度为Integer.MAX_VALUE

    keepAliveTime=0 说明只要线程个数比核心线程个数多并且当前空闲回收。

    java 复制代码
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
  • newCachedThreadPool:

    创建一个按需创建线程的线程池,初始线程个数为0,最多线程个数为Integer.MAX_VALUE,并且阻塞队列为同步队列;

    keepAliveTime=60 说明只要当前线程在60s内空闲则回收。

    这个类型的特殊之处在于加入同步队列任务会被马上执行,同步队列里面最多只有一个任务。

    java 复制代码
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

二、任务执行流程

java.util.concurrent.ThreadPoolExecutor#execute 这里其实就是任务提交给线程池之后,任务调度的过程。

java 复制代码
public class ThreadPoolExecutor extends AbstractExecutorService {
    public void execute(Runnable command) {
        // (1)如果任务为null ,则抛出NPE异常
        if (command == null)
            throw new NullPointerException();
        // (2)获取当前线程池的状态+线程个数变量的组合值
        int c = ctl.get();
        // (3)当前线程池中线程个数是否小于corePoolSize,小于则开启新线程运行
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // (4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
		   // (4.1)二次检查
            int recheck = ctl.get();
            // (4.2)如果当前线程池状态不是RUNNING则从队列中删除任务,并执行拒绝策略
            if (!isRunning(recheck) && remove(command))
                reject(command);
            // (4.3)否则如果当前线程池为空 则添加一个线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // (5)如果队列满,则新增线程,新增失败则执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }
}

代码执行步骤对应流程图:

三、addWorker方法

在主流程的addWorker方法主要做两件事情:

  • 第一部分双重循环的目的是通过 CAS 操作增加线程数;
  • 第二部分主要是把并发安全的任务加到 workers 里面,并且启动任务执行。
java 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    // 第一部分:双重循环的目的是通过 CAS 操作增加线程数;
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

       // (6)检查队列是否只在必要时为空
       // 下面条件等价于:
       // rs >= SHUTD8WN && 
 	   //       (rs !=SHUTDOWN           (Ⅰ) 当前线程池状态为:STOP、TIDYING或TERMINATED
	   //       || firstTask != null     (Ⅱ) 当前线程池状态为 SHUTDOWN 并且己经有了第一个任务
	   //       || workQueue.isEmpty())  (Ⅲ) 当前线程池状态为 SHUTDOWN 并且任务队列为空
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN   
             && firstTask == null                
             && !workQueue.isEmpty()))           
            return false;
	   
        // (7)循环CAS增加线程个数
        for (;;) {
            int wc = workerCountOf(c);
            // (7.1)如果线程个数超过最大线程数,则返回 false
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // (7.2)CAS增加线程个数,同时只有一个线程成功
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // (7.3)CAS 失败了,则看线程池状态是否变化了,变化则跳到外层循环重新尝试获取线程池状态,如果没有变化则还在内层循环重新CAS
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    // 第二部分:主要是把并发安全的任务加到 workers 里面,并且启动任务执行。
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    // (8)到这里说明 CAS成功了
    try {
        // (8.1)创建worker
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // (8.2)加独占锁,为了实现workers同步,因为可能多个线程调用了线程池的execute方法
            mainLock.lock();
            try {
                // (8.3)重新检查线程池状态,以避免在获取锁前调用了shutdown接口
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // (8.4)添加工作线程
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // (8.5)添加成功后则启动任务
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker方法执行流程图:

四、工作线程Worker

在第三小节中,代码8.1:为任务构建Worker(工作线程),代码8.4:添加Worker到工作线程集合中,代码8.5:启动线程。

对8.1分析:w = new Worker(firstTask);

java 复制代码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread;//Worker持有的线程
    Runnable firstTask;//初始化的任务,可以为null
    
    // (8.1)
    Worker(Runnable firstTask) {
        // 在构造函数内首先设置 Worker 的状态为 -1,这是为了避免当前 Worker 在调用
        // runWorker方法前被中断(当其他线程调用了线程池的 shutdownNow 时,如果 Worker
        // 状态 >=0,则会中断该线程)。
        // 这里设置了线程的状态为 -1 ,所以该线程就不会被中断了。
        setState(-1); // 在运行Worker之前禁止中断
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 创建一个线程
    }
    
    // (8.5) 启动线程就会来到这里,最终进入到runWorker方法
    public void run() {
        runWorker(this);
    }
    
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // (9)state设置为0, 允许中断
        boolean completedAbruptly = true;
        try {
            // (10)task就是firstTask,如果不为空,就先执行firstTask;如果为空,就调用getTask(),从阻塞队列中获取任务。
            while (task != null || (task = getTask()) != null) {
                // (10.1)
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
                    wt.interrupt();
                try {
                    // (10.2)执行任务前干一些事情
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();  // (10.3)执行任务
                    } 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); // (10.4)执行任务完毕后干一些事情
                    }
                } finally {
                    task = null;
                    // (10.5) 统计当前Worker(工作线程)完成了多少个任务
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
           // (11)执行清理工作
           //  来到这里,说明 当前不论是firstTask,还是阻塞队列中workQueue已经没有任务了。
           //  如果当前的工作线程由于抛出用户异常被终结,那么会新创建一个非核心线程。
           //  如果当前的工作线程并不是抛出用户异常被终结(正常情况下的终结),那么会这样处理:
           //      第一种:allowCoreThreadTimeOut为true,也就是允许核心线程超时的前提下,
           //             如果任务队列空,则会通过创建一个非核心线程保持线程池中至少有一个工作线程。
		 //      第二种:allowCoreThreadTimeOut为false,如果工作线程总数大于corePoolSize则直接返回,
           //             否则创建一个非核心线程,也就是会趋向于保持线程池中的工作线程数量趋向于corePoolSize。
           //             processWorkerExit()执行完毕之后,意味着该工作线程的生命周期已经完结。
           processWorkerExit(w, completedAbruptly);
        }
    }
}

​ Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。

​ thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务。

​ firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行阻塞队列(workQueue)中的任务,也就是非核心线程的创建。

  • 其实对应的就是 代码(10)的执行逻辑:

  • Worker.runWorker方法执行流程图:

  • processWorkerExit(w, completedAbruptly)

    java 复制代码
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        //     如果没有任何异常抛出的情况下是通过getTask()返回null引导线程正常跳出runWorker()方法的while死循环
        // 从而正常终结,这种情况下在getTask()中已经调用decrementWorkerCount()方法把线程数减1。
        //     因为抛出用户异常导致线程终结,这么completedAbruptly=true,这里会使工作线程数减1。
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();
    
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 全局的已完成任务记录数 + 此将要终结的Worker中的已完成任务数
            completedTaskCount += w.completedTasks;
            // 从工作线程集合中移除当前要销毁的Worker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        
        // 用于根据当前线程池的状态判断是否需要进行线程池terminate处理
        tryTerminate();
    
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) { // 没有异常的情况下completedAbruptly=false,这里会进入到if
                // 如果允许核心线程超时allowCoreThreadTimeOut=true,则线程数最小值为0,否则最小值为corePoolSize
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 如果最小值为0,同时任务队列不空,则更新最小值为1
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                // 如果工作线程数大于等于最小值,直接返回,不新增非核心线程
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 新增工作线程
            addWorker(null, false);
        }
    }
    • processWorkerExit方法大致做的事情:
      • 从工作线程集合中移除当前要销毁的Worker。
        • 这里的销毁,只是销毁了对线程的引用。
        • 因为线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。
      • 如果当前的工作线程由于抛出用户异常被终结,那么会新创建一个非核心线程。
      • 如果当前的工作线程并不是抛出用户异常被终结(正常情况下的终结),那么会这样处理:
        • 第一种:allowCoreThreadTimeOut为true,也就是允许核心线程超时的前提下,如果任务队列空,则会通过创建一个非核心线程保持线程池中至少有一个工作线程。
        • 第二种:allowCoreThreadTimeOut为false,如果工作线程总数大于corePoolSize则直接返回,否则创建一个非核心线程,也就是会趋向于保持线程池中的工作线程数量趋向于corePoolSize。
    • processWorkerExit()执行完毕之后,意味着该工作线程的生命周期已经完结。
相关推荐
FF在路上28 分钟前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进35 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人1 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.1 小时前
Mybatis-Plus
java·开发语言
不良人天码星1 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1702 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云2 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络2 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。2 小时前
Docker学习
java·开发语言·学习
如若1232 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python