线程池原理(一)线程池核心概述

news2024/9/21 18:53:04

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验

线程回顾


创建线程的方式

  • 继承 Thread 类
  • 实现 Runnable 接口

创建后的线程有如下状态:

NEW:新建的线程,无任何操作

public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + "is running"));
        Thread.State state = thread.getState();
        // 线程刚创建还未执行,状态为 NEW
        System.out.println(state);
    }

RUNNABLE:可执行的线程,在 JVM 中执行但是在等待操作系统的资源

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + "is running"));
    thread.start();
    Thread.State state = thread.getState();
    // 调用 start() 方法,可以执行,但不代表一定在执行
    System.out.println(state);
}

BOLCKED:阻塞,获取不到锁

public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (Test.class) {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (Test.class) {
            }
        });
        thread1.start();
        thread2.start();
        // 等待,保证 线程1 开始执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread.State state = thread2.getState();
        System.out.println(state);
    }

WAITING:等待,等待其他线程进行操作,时间不确定

public static void main(String[] args) {
        Thread thread = new Thread(LockSupport::park);
        thread.start();
        // 等待线程开始执行
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.State state = thread.getState();
        System.out.println(state);

        // 等待状态解除禁止,线程执行完毕
        LockSupport.unpark(thread);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        state = thread.getState();
        System.out.println(state);
    }

TIMED_WAITING:等待,等待的时间是确定的

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.State state = thread.getState();
        System.out.println(state);
    }

TERMINATED:终止,线程已经运行完毕

public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + "is running"));
        thread.start();
        try {
            // 等待线程执行完毕
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.State state = thread.getState();
        System.out.println(state);
    }

引入线程池


上述创建线程的方式存在如下缺陷:

  • 线程使用完后会被销毁,高并发场景下频繁创建和销毁线程的性能开销不可忽略
  • 无法控制线程并发数量,线程过多会导致 JVM 宕机

线程池是一种池化思想,由于创建和销毁线程需要时间,以及系统资源开销,我们需要一个“管理者"来统一管理线程及任务分配,减少这些开销,解决资源不足的问题。

在主要大厂的编程规范中,不允许在应用中自行显式地创建线程,线程必须通过线程池提供。


线程池解决了什么问题


  • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗:
  • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行:
  • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM
  • 节省CPU切换线程的时间成本(需要保持当前执行线程的现场,并恢复要执行线程的现场)。
  • 提供更强大的功能,延时定时线程池

线程池引发了什么问题


  • 异步任务提交后,如果JVM宕机,已提交的任务会丢失,需要考虑确认机制
  • 使用不合理可能导致内存溢出问题
  • 参数过多,代码结构引入数据结构与算法,增加使用难度

线程池概述


线程池继承结构


在这里插入图片描述

  • 最常用的是 ThreadPoolExecutor
  • 调度用 ScheduledThreadPoolExecutor,类似 Timer 和 TimerTask。
  • 任务拆分合并用 ForkJoinPool
  • Executors是工具类,协助创建线程池的

线程池工作状态


在这里插入图片描述

  • RUNNING(运行状态):这是线程池的初始状态。

    • 在此状态下,线程池接受新任务,并且也会处理等待队列中的任务。
    • 线程池的线程会一直运行,直到被转换到其他状态。
  • SHUTDOWN(关闭状态):当调用线程池的shutdown()方法时,线程池会进入此状态。

    • 在此状态下,线程池不接受新任务,但会继续处理等待队列中的任务。
    • 等待队列中的任务处理完毕后,线程池中的线程会逐渐结束,直到所有线程结束运行。
  • STOP(停止状态):当调用线程池的shutdownNow()方法时,线程池会进入此状态。

    • 在此状态下,线程池不接受新任务,同时也不处理等待队列中的任务
    • 而是尝试停止所有正在执行的任务,并且回收线程池中的所有线程。
  • TIDYING(整理状态):当所有的任务已经终止,workerCount(工作线程数)为0时,线程池会进入此状态。

    • 此时,会执行terminated()钩子方法,允许线程池执行一些收尾工作。
  • TERMINATED(终止状态):terminated()钩子方法执行完毕后,线程池会进入此状态。

    • 在终止状态下,线程池的任务完全结束,不再有任何活动。

七个核心参数


参数名描述
corePoolSize核心线程池基本大小,核心线程数
maximumPoolSize线程池最大线程数
keepAliveTime线程空闲后的存活时间
TimeUnit unit线程空闲后的存活时间单位
BlockingQueue workQueue存放任务的阻塞队列
ThreadFactory threadFactory创建线程的工厂
RejectedExecutionHandler handler当阻塞队列和最大线程池都满了之后的饱和策略

corePoolSize

核心线程数

  • 线程池刚创建时,线程数量为0,当每次执行 execute 添加新的任务时会在线程池创建一个新的线
    程,直到线程数量达到 corePoolSize 为止。
  • 核心线程会一直存活,即使没有任务需要执行,当线程数小于核心线程数时,即使有线程空闲,线程
    池也会优先创建新线程处理
  • 设置 allowCoreThreadTimeout=true (默认false)时,核心线程超时会关闭

maximumPoolSize

最大线程数

  • 当池中的线程数 >= corePoolSize ,且任务队列已满时。线程池会创建新线程来处理任务
  • 当池中的线程数 = maximumPoolSize ,且任务队列已满时,线程池会拒绝处理任务而抛出异常

如果使用无界的阻塞队列,该参数不生效


BlockingQueue

阻塞队列

  • 当线程池正在运行的线程数量已经达到 corePoolSize ,那么再通过 execute 添加新的任务则会被加到 workQueue 队列中
  • 任务会在队列中排队等待执行,而不会立即执行
  • 一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue , LinkedBlockingQueue , SynchronousQueue

keepAliveTime & TimeUnit

保活时间及其单位

  • 当线程空闲时间达到 keepAliveTime 时,线程会退出,直到线程数量 =corePoolSize
  • 如果 allowCoreThreadTimeout=true ,则会直到线程数量=0

keepAliveTime 是时间的大小,TimeUnit 是时间单位


ThreadFactory

线程工厂

  • 主要用来创建线程
  • 通过newThread()方法提供创建线程,该方法创建的线程都是“非守护线程”而且“线程优先级都是默认优先级”

RejectedExecutionHandler

拒绝策略

  • 当线程数已经达到 maxPoolSize ,且队列已满,会拒绝新任务
  • 当线程池被调用 shutdown() 后,会等待线程池里的任务执行完毕,再 shutdown 。如果在调用shutdown() 和线程池真正 shutdown 之间提交任务,会拒绝新任务
  • 当拒绝处理任务时线程池会调用 rejectedExecutionHandler 来处理这个任务。

如果没有设置默认是 AbortPolicy ,另外在 ThreadPoolExecutor 类有几个内部实现类来处理这类情况:

  • ThreadPoolExecutor.AbortPolicy :丢弃任务并抛出 RejectedExecutionException 异常。
  • ThreadPoolExecutor.CallerRunsPolicy :由调用线程处理该任务
  • ThreadPoolExecutor.DiscardPolicy :也是丢弃任务,但是不抛出异常
  • ThreadPoolExecutor.DiscardOldestPolicy :丢弃队列最前面的任务,然后重新尝试执行任务

另外实现 RejectedExecutionHandler 接口即可实现自定义的拒绝策略


线程池逻辑结构


线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部协调空闲的线程,如果有,则将任务交给某个空闲的华线程。

一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。在这里插入图片描述

当一个任务被提交,线程池会进行如下工作:

  • 首先判断当前的核心线程数量如果小于核心线程数,创建一个核心线程并执行任务

  • 如果大于核心线程数,则尝试将其放入等待队列,如果队列没有满则放入队列等待执行

  • 如果队列已满,则判断非核心线程数的数量+核心线程数是否小于最大线程数量

  • 小于:则创建一个非核心线程并执行任务(并不会取队列中的任务)

  • 大于:执行拒绝策略


线程池线程数设置


虽然使用线程池的好处很多,但是如果其线程数配置得不合理,不仅可能达不到预期效果,反而可能降低应用的性能。

因此按照任务类型分类,对不同的任务类型确定不同的线程数量。


任务类型分类

  • IO密集型任务:
    • 此类任务主要是执行 IO 操作。由于执行 lO 操作的时间较长,导致 CPU 的利用率不高,这类任务 CPU 常处于空闲状态。
    • Netty 的 IO 读写操作为此类任务的典型例子
  • CPU 密集型任务:
    • 此类任务主要是执行计算任务。由于响应时间很快,CPU 一直在运行,这种任务 CPU 的利用率很高。
    • 例如设计加密解密算法等大量需要 CPU 运算的场景
  • 混合型任务:
    • 此类任务既要执行逻辑计算,又要进行 IO 操作(如 RPC 调用、数据库访问)相对来说,由于执行 IO 操作的耗时较长(一次网络往返往往在数百毫秒级别),这类任务的 CPU 利用率也不是太高。
    • Web 服务器的 HTTP 请求处理操作为此类任务的典型例子

确定任务线程数

  • IO 密集型任务:

    • 由于 IO 密集型任务的CPU使用率较低,导致线程空余时间很多,因此通常需要开 CPU 核心数两倍的线程
    • Netty 的 IO 处理任务就是典型的 IO 密集型任务,所以,Netty 的 Reactor 实现类(定制版的线程池)的 IO 处理线程数默认正好为 CPU 核数的两倍
  • CPU密集型任务:

    • CPU 密集型的任务并行的任务越多,花在任务切换的时间就越多,CPU 执行任务的效率就越低,一般开等于 CPU 的核心数的线程数量
    • 比如 4 个核心的 CPU,通过 4 个线程并行地执行 4 个 CPU 密集型任务,此时的效率是最高的。但是如果线程数远远超出 CPU 核心数量,就需要频繁地切换线程,线程上下文切换时需要消耗时间,反而会使得任务效率下降。
  • 混合型任务:

    • 业界有一个比较成熟的估算公式,具体如下:

    • 最佳线程数 = ((线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间) * CPU核数
      
    • 通过公式可以看出:等待时间所占的比例越高,需要的线程就越多,CPU 耗时所占的比例越高,需要的线程就越少

    • 比如在 Web 服务器处理 HTTP 请求时,假设平均线程 CPU 运行时间为 100 毫秒,而线程等待时间(比如包括 DB 操作、RPC 操作作、缓存操作等)为 900 毫秒,如果 CPU 核数为 8 那么根据上面这个公式,估算如下:(900 毫秒+100 毫秒) / 100 毫秒 * 8 = 10 * 8 = 80,最好开 80 个线程

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

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

相关文章

嵌入式初学-C语言-十八

#接嵌入式初学-C语言-十七# 变量的生命周期 1. 概念:变量在程序运行中存在的时间 2. 根据变量存在的时间不同,变量可分为静态存储和动态存储 3. 变量的存储类型 变量的完整定义格式:[存储类型] 数据类型 变量列表; 4. 存储类型 auto&…

yolov8人脸识别案例

GitHub - wangWEI201901/YOLOv8-Detection-Project: 🛣️基于YOLOv8的智慧校园人脸识别和公路汽车检测

ITSM垂类下,企业如何逐步搭建一个好的AI Agent

随着企业数字化转型的不断深入,智能服务管理(ITSM)逐渐成为提升企业运营效率和服务质量的关键。企业纷纷探索如何将AI技术融入到IT服务管理中,以提升效率、降低成本并增强用户体验。成功实施AI并非易事,它要求企业在战…

探索全光网技术 | 全光网络技术方案选型建议五 (大厅场景)

目录 一、场景设计需求二、大厅场景拓扑三、部署方式四、产品相关规格说明五、方案优势与特点 注:本文章参考资料为:华三官方资料 - “新华三全光网络3.0解决方案(教育)”与 锐捷官方资料 - “【锐捷】高校极简以太全光3.X方案设计…

web基础与http协议与配置

目录 一、web基础 1.1 DNS与域名(详解看前面章节) 1.2 网页的概念(HTTP/HTTPS) 1.2.1 基本概念 1.2.2 HTML文档结构(了解) 1.2.3 web相关重点 1.2.4 静态资源和动态资源 二、http协议 2.1 概述 2.2 cookie和session&…

【HarmonyOS NEXT星河版开发学习】小型测试案例10-计数器案例

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面(暂未发布) 前言 鸿蒙开发中的点击事件是一个基础且重要的功能,它使得应用能够响应用户的各种触摸操作。通过对点击事件及其相关参数的深入…

扎克伯格说AI会让推荐系统变得更强大?一文读懂什么是智能推荐系统

导语:我认为很少有人意识到,推荐系统是世界上构想过的最大的计算系统之一。——Jensen Huang 前言 扎克伯格在 2024 年 7 月说,我们在组建 Reality Labs 之前就成立了 FAIR——人工智能研究中心,主要产品线有 Facebook 和 Insta…

006集—— 修饰符(public、private 、internal等)——C#学习笔记

封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。 抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。 C…

Python面试宝典第30题:找出第K大元素

题目 给定一个整数数组nums&#xff0c;请找出数组中第K大的数&#xff0c;保证答案存在。其中&#xff0c;1 < K < nums数组长度。 示例 1&#xff1a; 输入&#xff1a;nums [3, 2, 1, 5, 6, 4], K 2 输出&#xff1a;5 示例 2&#xff1a; 输入&#xff1a;nums …

python如何判断文件有多少行

如何统计读取的一个txt文本的行数呢&#xff1f; 最简单的办法是把文件读入一个大的列表中&#xff0c;然后统计列表的长度。如果文件的路径是以参数的形式filepath传递的&#xff0c;那么只用一行代码就可以完成我们的需求了&#xff1a; count len(open(filepath,rU).readl…

数字样机:惯性导航系统控制单元仿真

01.简介 惯性导航系统 (INS&#xff0c;Inertial Navigation System) 基于惯性原理建立&#xff0c;而惯性是物体自身的固有属性&#xff0c;因此其工作时既不依赖于外部信息&#xff0c;也不向外部辐射能量&#xff0c;优于卫星导航与无线电导航&#xff0c;是一种具备隐蔽性…

KEYSIGHT E5063A-006 无线功率传输分析

KEYSIGHT是德 E5063A-006 无线功率传输分析 E5063A-006 无线功率传输分析选件能够以任意负载阻抗设置&#xff0c;实时测量线圈或谐振器之间的无线功率传输效率&#xff08;WPT&#xff09;。基于测量结果进行的 2D/3D 分析&#xff0c;能够帮助用户更轻松地了解对负载阻抗的…

Selenium + Python 自动化测试07(滑块的操作方法)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 本篇文章主要讲述如何操作滑块。 目前很多系统登录或者注册的页面都有滑块相关的验证&#xff0c;selenium 中对滑块的基本操作采用了元素的拖曳的方式。需要用到Actiochains模…

市场惊人逆转:西格尔改口称降息紧迫性已减

最近&#xff0c;市场发生了惊人的逆转&#xff0c;让很多分析师和投资者感到意外。沃顿商学院教授**杰里米西格尔&#xff08;Jeremy Siegel&#xff09;**在短短三天内就改变了他对美联储政策的看法。此前&#xff0c;他曾呼吁美联储迅速降息&#xff0c;但现在他认为这种紧急…

五,搭建环境:辅助功能

五&#xff0c;搭建环境&#xff1a;辅助功能 文章目录 五&#xff0c;搭建环境&#xff1a;辅助功能编写登录失败异常编写常量类MD5 工具 (加密工具类)日志配置文件 编写登录失败异常 我们在 demo-module04-util 模块下&#xff0c;创建一个名为&#xff1a;com.rainbowsea.i…

10、MySQL-索引

目录 1、索引概述 2、索引结构 2.1 BTree 2.2 BTree 2.3 Hash 3、索引分类 4、索引语法 4.1 创建索引 4.2 查看索引 4.3 删除索引 5、SQL性能分析 5.1 SQL执行频率 5.2 慢查询日志 5.3 profile详情 5.4 explain执行计划 6、索引使用 6.1 验证索引效率 6.2 最左…

浪潮云服务器(Inspur)硬件监控指标解读

随着企业业务的快速发展&#xff0c;服务器的稳定运行变得愈发重要。浪潮云服务器以其高性能和稳定性&#xff0c;在数据中心中扮演着关键角色。为了确保服务器的稳定运行&#xff0c;监控易作为一款专业的IT基础设施监控软件&#xff0c;为浪潮云服务器提供了全面的硬件监控解…

Swift 中的函数式核心与命令式外壳:单向数据流

文章目录 前言函数式核心命令式外壳副作用可运行 Demo函数式核心部分命令式外壳部分副作用处理SwiftUI 界面代码运行截图代码解释 总结参考资料 前言 之前&#xff0c;我们讨论了在 Swift 中的函数式核心与命令式外壳的概念。其目标是通过值类型提取纯逻辑&#xff0c;并将副作…

Java智能之Spring AI:5分钟打造智能聊天模型的利器

前言 尽管Python最近成为了编程语言的首选&#xff0c;但是Java在人工智能领域的地位同样不可撼动&#xff0c;得益于强大的Spring框架。随着人工智能技术的快速发展&#xff0c;我们正处于一个创新不断涌现的时代。从智能语音助手到复杂的自然语言处理系统&#xff0c;人工智能…

【卫星遥感影像】国产遥感影像分类技术应用研究进展综述_论文推荐

影像分类是遥感影像信息提取中的基本问题之一和遥感影像应用的关键&#xff0c;为我国掌握本土信息资源自主权、满足国家的紧迫需求具有重大战略意义。本文将进行这篇遥感影像分类的论文推荐。 1. 论文引用 [1]胡杰,张莹,谢仕义.国产遥感影像分类技术应用研究进展综述[J].计算…