线程池ThreadPoolExecutor,从0到0.6

news2025/7/19 16:52:19

ThreadPoolExecutor是JDK提供的在java.util.concurrent包中的一个用于创建线程池的工具类。

 一、ThreadPoolExecutor的7个参数

  •  corePoolSize:核心线程数,线程池中保留的最小的线程数量,即使它们是空闲的也不会被销毁,除非allowCoreThreadTimeOut被设置为true;
  • maximumPoolSize:最大线程数,线程池中可以创建的最大线程数量;
  • keepAliveTime:空闲线程存活时间,当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最大时间;
  • unit:keepAliveTime参数的时间单位;

枚举类TimeUnit:

  • NANOSECONDS:纳秒
  • MICROSECONDS:微妙
  • MILLISECONDS:毫秒
  • SECONDS:秒
  • MINUTES:分钟
  • HOURS:小时
  • DAYS:天
  • WorkQueue:工作队列,用于保存待执行任务的队列,这个队列将只保存execute方法提交的可运行任务

JDK提供的四种工作队列:

  •  ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序,新任务进来后会放到队尾;
  • LinkedBlockingQueue:基于链表的可选的无界阻塞队列(最大容量为Integer.MAX,可设定长度),按FIFO排序;
  • SynchronousQueue:不缓存任务的阻塞队列,新任务进来时不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略;
  • PriorityBlockingQueue:具有优先级的无界阻塞队列

有界队列:可防止资源耗尽问题,当线程池中线程数量达到corePoolSize后,再有新任务进来,则会讲任务放入该队列尾部等待被调度,如果队列已满,则创建一个新线程,如果线程数量的达到maxPoolSize,则执行拒绝策略;

无界队列:当线程池中线程数达到corePoolSize后,再有新任务进来会一直存入该队列,不会创建线程,所以线程数量永远不会达到maxPoolSize,此时maxPoolSize不起作用。

  • threadFactory:线程工厂,创建新线程时要使用的工厂,该工厂可以用来设定线程名、是否为daemon线程等;
  • handler:拒绝策略,当达到线程池中的线程数量达到最大线程数,并且工作队列中的任务已达到最大容量,再有新任务提交进来而阻塞执行时要使用的处理程序。

拒绝策略:

AbortPolicy:ThreadPoolExecutor的默认处理方式,直接抛出RejectedExecutionException异常;

DiscardPolicy:不处理直接丢弃任务,不会给任何通知;

DiscardOldestPolicy:丢弃队列中最早未处理的任务,即是丢弃队列头部的一个任务;

CallerRunsPolicy:在当前调用者的线程中运行任务,即是谁提交的任务由它自己去处理。

二、从源码分析线程池执行流程

1、线程池中主要成员变量

  • ctl:初始化了线程池的状态和线程数量,初始状态为RUNNING并且线程数量为0;这里一个Integer既包含了状态也包含了数量,其中int类型一共32位
    • 高3位标识状态
    • 低29位标识数量
    • ctlOf(),395行,用来更新线程状态和数量
  • COUNT_BITS:线程数量的最大取值长度为29,所以线程数量最多为2^29-1
  • CAPACITY:线程池容量,=(1<<COUNT_BINTS)-1
  • RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED:ctl高三位表示的线程池的五种状态

线程池状态转换图:

  • runStateOf(int c):通过传入的c,获取最高三位的值,拿到线程池状态码
  • workerCountOf(int c):获取当前线程数量,拿到现有线程数量

2、线程池的execute()方法执行过程

众所周知,我们需要调用线程池的execute()并传入线程,以能在将来某个时间执行传入的任务;所以,首先执行的是execute方法。

1343-1344:健壮性判断,传入的要执行的任务不能为空;

1365:获取ctl的具体值,包括线程池的状态和线程数量;

1366-1370:判断工作线程数是否少于核心线程数,如果少于,说明未达到核心线程数,则创建新的线程;

        1367:如果创建成功,execute()方法结束;否则重新获取ctl的值:

如果工作线程不少于核心线程数(说明线程数已达到核心线程数)或工作线程少于核心线程数但新线程创建失败,则:

        1371-1379:如果线程池为RUNNING状态,同时能成功将任务添加到任务队列中

                1372:重新获取ctl的值

                1373-1374:再次判断线程池状态是否为RUNNING,如果非RUNNING就执行remove将该任务从阻塞队列中删除

                        1374:根据拒绝策略执行拒绝任务,execute()结束

                1375-1376:如果线程池状态不为RUNNING,判断工作线程数,如果工作线程数为0说明任务队列中有任务,但没有线程在执行任务,此时创建一个线程;

        1378-1379:如果线程池状态不为RUNNING或状态为RUNNING但不能入队列,就创建线程数,如果创建失败则根据策略拒绝任务,execute()结束。

调用线程池execute()方法添加一个任务时,总结:

如果有空闲线程,使用空闲线程直接执行该任务;

如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的核心线程执行该任务;

如果没有空闲线程,且当前线程数等于corePoolSize,同时任务队列未满,则将任务放入任务队列等候;

如果没有空闲线程,且任务队列已满,同时线程池中的线程数小于maxinumPoolSize,则创建新的线程执行任务;

如果没有空闲线程,且任务队列已满,同时线程池中的线程数等于maxinumPoolSize,则根据拒绝策略拒绝新的任务。

3、线程池addWorker方法执行的流程

private boolean addWorker(Runnable firstTask, boolean core) {
    // for循环的标志
    retry:
    for (; ; ) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 查看是否可以不去构建新的工作线程:如果线程池状态不是RUNNING并且线程池状态不是SHUTDOWN、任务不为空、工作队列为null,就不用去创建新线程了
        if (rs >= SHUTDOWN // 如果线程状态不是RUNNING
                &&
                !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
                // 线程不是SHUTDOWN状态(那也就是STOP,TIDYING,TERMINATED三者之一)
                // 任务不为空 -> 这里对应上述的addWorker(null,false)
                // 工作队列为null
                // 这时直接return false,不去构建新的工作线程
            return false;
        for (; ; ) {
            // 获取工作线程数
            int wc = workerCountOf(c);
            if (wc >= CAPACITY || // 如果工作线程大于最大线程容量
                    wc >= (core ? corePoolSize : maximumPoolSize)) // 或者当前线程数达到核心/最大线程数的要求
                return false; // 直接结束,添加Worker失败。
            if (compareAndIncrementWorkerCount(c)) // 以CAS的方式添加线程
                break retry; // 如果成功,跳出最外层循环
            c = ctl.get(); // 再次读取ctl的值
            if (runStateOf(c) != rs) // 如果运行状态不等于最开始查询到的rs,那就从头循环一波。
                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); // 构建Worker对象传入任务
        final Thread t = w.thread; // 将worker线程取出
        if (t != null) {
            // 拿到全局锁,避免我在添加任务时,其他线程干掉线程池,因为干掉线程池需要获取到这个锁
            final ReentrantLock mainLock = this.mainLock;
            // 加锁
            mainLock.lock();
            try {
                // 获取线程池状态
                int rs = runStateOf(ctl.get());
                // 两种情况允许添加工作线程
                if (rs < SHUTDOWN || // 判断线程池是否是RUNNING状态
                        (rs == SHUTDOWN && firstTask == null)) { // 如果线程池状态为SHUTDOWN并且任务为空
                    if (t.isAlive()) // 如果线程正在运行,直接抛出异常
                        throw new IllegalThreadStateException();
                    // 添加任务线程到workers中
                    workers.add(w);
                    // 获取任务线程个数
                    int s = workers.size();
                    // 如果任务线程大于记录的当前出现过的最大线程数,替换一下。
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true; // 任务添加的标识设置为true
                }
            } finally {
                mainLock.unlock(); //任务添加成功,退出锁资源
            }
            if (workerAdded) { // 如果添加成功
                t.start(); // 启动线程
                workerStarted = true; // 标识启动标识为true
            }
        }
    } finally {
        if (!workerStarted) // 如果线程启动失败
            addWorkerFailed(w); // 移除掉刚刚添加的任务
    }
    return workerStarted;
}
————摘自郑金维老师的讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/395502.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Modbus转profinet网关连接1200PLC在博图组态与驱动器通讯程序案例

本案例给大家介绍由兴达易控modbus转profinet网关连接1200PLC在博图软件无需编程&#xff0c;实现1200Profinet转modbus与驱动器通讯的程序案例 硬件连接&#xff1a;1200PLC一台&#xff1b;英威腾DA180系列驱动器一台&#xff1b;兴达易控modbus转profinet网关一台 下面就是…

【Git】拉取 Pull Requests 测试的两种方法

文章目录前言参考目录方法说明方法一&#xff1a;直接拉取方法二&#xff1a;使用 diff 文件2.1、保存 diff 文件2.2、新建分支并执行文件前言 最近有参与到框架帮忙进行简单的 Pull Requests&#xff08;以下简称 PR&#xff09; 测试&#xff0c;因为也是第一次接触到这种操…

代码随想录 动态规划||01背包理论 416

Day3601背包理论基础01背包有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来…

Java学习笔记 --- Servlet(1)

一、Servlet技术 1、Servlet基本介绍 1、Servlet 是 JavaEE 规范之一。规范就是接口 2、Servlet 就 JavaWeb 三大组件之一。三大组件分别是&#xff1a;Servlet 程序、Filter 过滤器、Listener 监听器。 3、Servlet 是运行在服务器上的一个 java 小程序&#xff0c;它可以…

院士交锋,专家论道|NLP大模型技术与应用十大挑战,剑指AI未来

2023年2月24日下午&#xff0c;第四届OpenI/O启智开发者大会NLP大模型分论坛在深圳人才研修院隆重举办。NLP大模型论坛会议现场众多NLP领域顶级专家学者与多家国产NLP大模型开发团队汇聚一堂&#xff0c;学术界与产业界破圈交流&#xff0c;激荡尖端思想、分享前沿动态&#xf…

Linux学习第二十二节-网卡IP设置

1.修改网卡IP地址 方式一&#xff1a;通过修改网卡配置文件修改 网卡配置文件位置&#xff1a; /etc/sysconfig/network-scripts/网卡名 #ifconfig 表示用于显示和设置网卡的参数 #ip addr 表示用于显示和设置网卡的参数 #systemctl restart network 表示重启网络 …

Spark Join大小表

Spark Join大小表无法广播过滤后大小表数据分布均匀大小表 : 大小表尺寸相差 3 倍以上 Join 优先考虑 BHJ小表的数据量 > 广播阈值时&#xff0c;优先考虑 SHJ 无法广播 大表 100GB、小表 10GB&#xff0c;都远超广播变量阈值 当小表的尺寸 > 8GB时&#xff0c;创建广…

剑指-Offer-30-包含min函数的栈

剑指 Offer 30.包含min函数的栈 题目描述&#xff1a; 定义栈的数据结构&#xff0c;请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中&#xff0c;调用 min、push 及 pop 的时间复杂度都是 O(1)。 示例&#xff1a; MinStack minStack new MinStack(); minSt…

Python中的错误是什么,Python中有哪些错误

7.1 错误(errors) 由于Python代码通常是人类编写的&#xff0c;那么无论代码是在解释之前还是运行之后&#xff0c;或多或少总会出现一些问题。 在Python代码解释时遇到的问题称为错误&#xff0c;通常是语法和缩进问题导致的&#xff0c;这些错误会导致代码无法通过解释器的解…

2023年绿色建筑国际会议(ICoGB 2023)

2023年绿色建筑国际会议&#xff08;ICoGB 2023&#xff09; 重要信息 会议网址&#xff1a;www.icogb.org 会议时间&#xff1a;2023年5月19-21日 召开地点&#xff1a;斯德哥尔摩 截稿时间&#xff1a;2023年4月1日 录用通知&#xff1a;投稿后2周内 收录检索&#xff…

剑指 Offer 61 扑克牌中的顺子

摘要 扑克牌中的顺子 一、集合 Set 遍历 根据题意&#xff0c;此5张牌是顺子的 充分条件 如下&#xff1a; 除大小王外&#xff0c;所有牌 无重复 &#xff1b;设此5张牌中最大的牌为max&#xff0c;最小的牌为min&#xff08;大小王除外&#xff09;&#xff0c;则需满足…

深入理解浏览器解析机制和XSS向量编码

目录 1、HTML解析 字符实体(character entities) HTML字符实体(HTML character entities) 字符引用(character references) 在HTML中有五类元素 五类元素的区别如下 深入了解下RCDATA元素 2、URL解析 3、JavaScript解析 4、解析流 1、HTML解析 从XSS的角度来说&…

es倒排索引原理

1、简介 网上看到的一篇文章&#xff0c;对Lucene的倒排索引是如何执行的&#xff0c;说的比较易懂&#xff0c;就转过来分享下。 Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。特别是它对多条件的过滤支持非常好&#xff0c;比如年龄在18和30之间&a…

kubeadm安装K8S(集群)

前言市面上很多k8s的安装工具&#xff0c;作为产品的设计者和推广者&#xff0c;K8S组织也知道自己的产品部署起来十分的困难&#xff0c;于是把开源爱好者写的工具kubeadmn收编为正规军&#xff0c;纳入到了自己的麾下。为什么我们要用kubeadmn来部署&#xff1f;因为kubeadm不…

【代码实践】DeepBDC for few-shot learning代码运行

DeepBDC是Jiangtao Xie等人在CVPR2022上提出的few-shot classification方法&#xff0c;论文全名为“Joint Distribution Matters: Deep Brownian Distance Covariance for Few-Shot Classification”。本文旨在记录在Window系统下运行该官方代码&#xff08;https://github.co…

Linux学习第二十四节-Podman容器

一、容器的概念 容器是由一个或多个与系统其余部分隔离的进程组成的集合。我们可以理解为“集装箱”。 集装箱是打包和装运货物的标准方式。它作为一个箱子进行标记、装载、卸载&#xff0c;以及从一个 位置运输到另一个位置。该容器的内容与其他容器的内容隔离&#xff0c…

传统企业数字化转型真的有必要吗?应该如何做转型?

随着数字经济的快速发展&#xff0c;各行各业数字化转型成为必然。从最初的信息化建设&#xff0c;到数字企业、数字政府建设&#xff0c;再到如今的数字经济建设&#xff0c;传统企业在数字化转型中的作用越来越大。与此同时&#xff0c;数字化转型对传统企业提出了更高的要求…

【Java开发面试】AHXX面试总结

1. java中常用的集合有哪些 java中常用的集合类有List,Set,Map,其中List和Set继承了Collection。 List的实现类有&#xff1a;ArrayList&#xff0c;LinkedList&#xff0c;Vector&#xff0c;Stack Set的实现类有&#xff1a;TreeSet&#xff0c;HashSet Map的实现类有&#…

MySQL运维篇之读写分离

04、读写分离 4.1、介绍 读写分离&#xff0c;简单地说是把对数据库的读和写操作分开&#xff0c;以对应不同的数据库服务器。主数据库提供写操作&#xff0c;从数据库提供读操作&#xff0c;这样能有效地减轻单台数据库的压力。 通过Mycat即可轻易实现上述功能&#xff0c;…

02_Linux终端操作,shell命令,软件安装,文件系统结构,磁盘管理

目录 终端操作 常用Shell命令 Ubuntu软件安装方法 Ubuntu文件系统结构 绝对路径和相对路径 Ubuntn下磁盘管理 终端操作 打开终端快捷键Ctrlaltt 或鼠标右键 常用Shell命令 1.目录信息查看命令ls ls -a 显示目录所有文件及文件夹,包括隐藏文件,比如以.开头的 ls -l…