简单分享线程池的设计

news2024/9/20 21:37:20

温故而知新,可以为师矣。

线程池是什么

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。

池化思想,就是为了提高对资源的利用率,减少对资源的管理,不用思考细枝末节,直接拿来就用。它的应用有很多,比如数据库连接池,内存池等等。

线程池的好处以及使用

1.线程池的好处

使用线程池有着很多好处,比如我们无需关心线程池内部线程的调度,只要按照设定好的参数去配置即可快速使用,线程池可以按照既定的规则去分配资源,执行对应的任务。针对于线程池的好处,在美团的《Java线程池实现原理及其在美团业务中的实践》中是这样描述的:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

同时,《阿里巴巴 Java 开发手册泰山版》中有着这样的强制规定:

【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

2.线程池的使用

线程池的创建可以分为两类,通过 Executors 或者 ThreadPoolExecutor 来创建。由于通过 Executors 来创建线程池使用的是默认参数,存在各样弊端,因此在《阿里巴巴 Java 开发手册泰山版》被强制规定不允许通过 Executors 来创建,他的目的是让使用者将设置的参数暴露出来,能够更加清晰地使用:

  • newFixedThreadPool和 newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
  • newCachedThreadPool和 newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

因此这里只讨论 ThreadPoolExecutor 的使用,使用的是 JDK1.8。

3.通过 ThreadPoolExecutor 创建线程池

目前可通过 ThreadPoolExecutor 来创建 7 种线程池:
在这里插入图片描述

这里只给出创建 ArrayBlockingQueue 的例子:

public class ThreadPoolDemo {  
  
    private static ExecutorService pool = new ThreadPoolExecutor(4, Runtime.getRuntime().availableProcessors() * 2,  
            0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(Integer.MAX_VALUE), r -> new Thread(r, "ThreadTest"));  
  
    public static void main(String[] args) {  
        for (int i = 0; i < 100; i++) {  
            pool.execute(new Task(i));  
        }  
    }  
}  
  
class Task implements Runnable{  
  
    private int i;  
  
    Task(int i){  
        this.i = i;  
    }  
  
    @Override  
 public void run() {  
        System.out.println(Thread.currentThread().getName() + i);  
    }  
}

结果:

····
ThreadTest13
ThreadTest18
ThreadTest17
ThreadTest16
ThreadTest21
ThreadTest20
ThreadTest19
ThreadTest24
ThreadTest23
····

4.线程池的核心参数

ThreadPoolExecutor 的核心参数总共有 7 个:

public ThreadPoolExecutor(int corePoolSize,  
                          int maximumPoolSize,  
                          long keepAliveTime,  
                          TimeUnit unit,  
                          BlockingQueue<Runnable> workQueue,  
                          ThreadFactory threadFactory,  
                          RejectedExecutionHandler handler){
                          ····
                          }
参数名解释作用
corePoolSize线程池核心线程大小线程池维护的最小线程数量,即使线程处于空闲状态,也不会被销毁,除非设置了「allowCoreThreadTimeOut」。
maximumPoolSize线程池最大线程数量当线程数已经达到了「corePoolSize」,此时如果有任务继续提交到线程池,会将任务添加到阻塞队列中,如果设置的阻塞队列也满了,则会重新申请新的线程来执行任务,一直到最大线程数量
keepAliveTime空闲线程存活时间一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁
unit空闲线程存活时间单位NANOSECONDS :1微毫秒 = 1微秒 / 1000 MICROSECONDS :1微秒 = 1毫秒 / 1000 MILLISECONDS :1毫秒 = 1秒/1000 SECONDS :秒 MINUTES :分 HOURS :小时 DAYS :天
workQueue工作队列当线程池的所有线程都在处理任务时,若来了新任务则会缓存到此任务队列中,然后等待执行
threadFactory线程工厂创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
RejectedExecutionHandler拒绝策略当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,此时针对于任务的拒绝策略

线程池的设计和实现

上面简单介绍了线程池的一些使用和参数,下面主要看下线程池对于如何处理线程的执行:
![[Pasted image 20220610154648.png]]

通过图中,可以简单理解为,如果任务提交到线程池中,线程池会根据一些策略来决定是立刻执行任务还是放在任务缓存队列中。具体的执行策略规则是由线程池中「线程数」和「队列」来决定的:
![[Pasted image 20220610155321.png]]

关于线程池的源码解析等,不是本次分享的重点,网上有很多文章内容可以参考学习,这里给一篇比较好的分析文章: Java线程池源码解析

线程池参数设置参考方案

1.为什么要考虑线程池的参数设置?

先来看下因为线程池参数设置导致的事件:
1)美团-2018年XX页面展示接口大量调用降级:
事故描述:XX页面展示接口产生大量调用降级,数量级在几十到上百。
事故原因:该服务展示接口内部逻辑使用线程池做并行计算,由于没有预估好调用的流量,导致最大核心数设置偏小,大量抛出RejectedExecutionException,触发接口降级条件。
![[Pasted image 20220610161817.png]]

2)美团-2018年XX业务服务不可用S2级故障
事件描述:XX业务提供的服务执行时间过长,作为上游服务整体超时,大量下游服务调用失败。
事故原因:该服务处理请求内部逻辑使用线程池做资源隔离,由于队列设置过长,最大线程数设置失效,导致请求数量增加时,大量任务堆积在队列中,任务执行时间过长,最终导致下游服务的大量调用超时失败。
![[Pasted image 20220610161908.png]]

上面对线程池使用不当造成的事故,会让我们对线程池的参数设置有一个思考,怎样设置参数才是最好的,这其中包括了对系统性能的思考。比如:

  1. 线程池的线程数量设置过多,会使线程竞争激烈,影响系统性能
  2. 线程池的线程数量过少,会浪费系统资源。这里还涉及到任务的耗时情况,假设任务耗时较长,且使用了无界队列,会导致容器内存 OOM。

这里普及一个小知识:线程和 CPU 核数有什么关系?
CPU 核数和线程数并没有什么关系,单核 CPU 一样可以执行多个线程,只是单核 CPU 同一时间只能执行一个线程,多线程会增加线程上下文切换。

2.参数设置

网上关于参数设置的讨论分为四个方面。

2.1 根据业务进行推算

一个任务的执行时间是0.1s,那么1s内可以执行10个,500个任务就需要500/10 = 50个,即 n / (1/每个任务花费时间) = n * 每个任务花费时间
那么至少要设置50~100个线程,28原则设置为80

这种估算比较粗糙,由于业务发展,业务的执行时间不能够完全确定,而且任务的数量也可能随着时间的变化而变化。

2.2 根据 CPU 和 IO 进行推算

1)CPU 密集型:最佳线程数 = CPU 核数 +1

对于 CPU 密集型场景,理论上“线程的数量 =CPU 核数”就是最合适的。不过在实际项目中,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。

2)IO 密集型:最佳线程数=2 * CPU 核数

因为IO设备的读写速度远低于CPU的执行速度,所以IO密集型的任务执行时间要比CPU密集型长很多。而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,可以多配置一些线程,根据经验具体的计算方法是:最佳线程数=2*CPU核数

3)IO密集型和CPU密集型交叉运行:最佳线程数 =CPU 核数 * ( 1 +(I/O 耗时 / CPU 耗时))

对于 I/O 密集型和CPU密集型交叉运行的场景,最佳的线程数是与程序中 CPU 计算和 I/O 操作的耗时比相关的,可以总结出一个公式:最佳线程数 =1 +(I/O 耗时 / CPU 耗时)
。令 N=I/O 耗时 / CPU 耗时,当一个线程 执行 IO 操作时,另外 N个线程正好执行完各自的 CPU 计算,这样 CPU 的利用率就达到了 100%。

2.3 根据 QPS 和 P99 估算

在Hystrix文档中,官方这样建议去设置核心线程数:

requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room

每秒最大支撑的请求数 * 99%平均响应时间 + 缓冲值
链接如下:https://github.com/Netflix/Hystrix/wiki/Configuration#ThreadPool

假如每秒可以支撑 100 个请求,99%的响应时间为0.1s,缓冲值为4,那么计算公式为:100 * 0.1s + 4 = 14个最大线程。

2.4 动态设置线程池参数

参数设置的核心难点是,由于业务的变更和发展,并且随着容器化的推进,不容易估计业务所需要的参数,可能会造成一些问题。美团在Java线程池实现原理及其在美团业务中的实践中提出了动态化线程池的设计,在原生的线程池基础上,增加了动态化配置参数和监控,能够实时监控和优化线程池的使用情况。

动态化线程池设计:https://github.com/mabaiwan/hippo4j

以目前的情况来看,这种方式可能是最好的。

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

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

相关文章

MySQL---空间索引、验证索引、索引特点、索引原理

1. 空间索引 MySQL在5.7之后的版本支持了空间索引&#xff0c;而且支持OpenGIS几何数据模型 空间索引是对空间数据类型的字段建立的索引&#xff0c;MYSQL中的空间数据类型有4种&#xff0c;分别是&#xff1a; 类型 含义 说明 Geometry 空间数据 任何一种空间类型 Poi…

HCIA-VRP系统

目录 一&#xff0c;什么是VRP VRP提供的功能&#xff1a; VRP文件系统&#xff1a; VRP存储设备&#xff1a; 设备初始化过程&#xff1a; 设备管理方式&#xff1a; 1&#xff0c;Web界面&#xff1a;可视化操作&#xff0c;通过http和https登录&#xff08;192.168.1.…

信息安全工程复习

目录 第二章 从口令系统说起 2.1 身份鉴别常见手段及例子 2.2 多因子认证 2.3 计时攻击 2.4 口令机制 2.5 假托和钓鱼 第三章 安全协议 3.1 认证协议 3.2 安全协议攻击 3.3 密钥分配协议 3.4 课后作业 第四章 访问控制 4.1 概念 4.2 控制访问三要素 4.3 控制访问…

Windows服务

参考地址&#xff1a;https://www.cnblogs.com/2828sea/p/13445738.html 1. 新建服务 2. 在 service 下 添加安装程序 会自动添加 修改这两个文件属性&#xff1a; serviceInstaller1&#xff1a; DelayedAutoStart:是否自动启动Descrition:介绍服务&#xff08;自定义&…

chatgpt赋能Python-python3_图片处理

Python3图片处理&#xff1a;简单高效的图像处理工具 Python3作为一种高级编程语言&#xff0c;在科学、金融、工程等领域中广受欢迎。它具有简洁的语法、快速的开发速度、多样化的应用场景等特点。其中&#xff0c;Python3在图像处理方面也非常出色&#xff0c;本文将介绍Pyt…

pg事务:事务的处理

事务的处理 事务块 从事务形态划分可分为隐式事务和显示事务。隐式事务是一个独立的SQL语句&#xff0c;执行完成后默认提交。显示事务需要显示声明一个事务&#xff0c;多个sql语句组合到一起称为一个事务块。 事务块通过begin&#xff0c;begin transaction&#xff0c;st…

【学习日记2023.5.20】 之 菜品模块完善

文章目录 3. 功能模块完善之菜品模块3.1 公共字段自动填充3.1.1 问题分析3.1.2 实现思路3.1.3 代码开发1.3.1 步骤一1.3.2 步骤二1.3.3 步骤三 3.1.4 功能测试3.1.5 提交代码 3.2 新增菜品3.2.1 需求分析与设计3.2.2 代码开发3.2.2.1 文件上传实现3.2.2.2 新增菜品实现 3.2.3 功…

pg事务:快照

pg中的快照 快照&#xff08;snapshot&#xff09;是记录数据库当前瞬时状态的一个数据结构。pg数据库的快照保存当前所有活动事务的最小事务ID、最大事务ID、当前活跃事务列表、当前事务的command id等 快照数据保存在SnapshotData结构体类型中&#xff0c;源码src/include/u…

PyQt5桌面应用开发(16):定制化控件-QPainter绘图

本文目录 PyQt5桌面应用系列画画图&#xff0c;喝喝茶QPainter和QPixmapQPixmapQPainter绘制事件 一个魔改的QLabelCanvas类主窗口主程序&#xff1a; 总结 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开发&#xff08;2…

深入了解vector

vector 1. vector的介绍及使用1.1 vector的介绍1.2 vector的使用1.2.1 vector的定义&#xff08;(constructor)构造函数声明&#xff09;1.2.2 vector iterator 的使用1.2.3 vector Capacity1.2.4 vector Modifiers1.2.4 vector 迭代器失效问题 2. vector模拟实现 1. vector的介…

快速排序的三种方法

今日复习了一下快速排序的算法。 hoare法 快速排序由Hoare在1960年提出。它的基本思想是&#xff1a;通过排序将需要排序的数据分割成独立的两部分&#xff0c;左边的所有数据都比右边的小&#xff0c;然后再按此方法对这两部分数据分别进行快速排序递归&#xff0c;使其变成有…

时间序列预测 | 基于秃鹰算法优化BP神经网络(BES-BP)的时间序列预测,matlab代码

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 基于秃鹰算法优化BP神经网络(BES-BP)的时间序列预测,matlab代码 评价指标包括:R2、MAE、MSE、RMSE等,代码质量极高,方便学习和替换数据。 部分源码 %% 清空环境变量 warning off % 关闭报警信息…

BurpSuite—-Spider模块(蜘蛛爬行)

本文主要介绍BurpSuite—-Spider模块(蜘蛛爬行)的相关内容 关于BurpSuite的安装可以看一下之前这篇文章&#xff1a; http://t.csdn.cn/0Qw2n 一、简介 Burp Spider 是一个映射 web 应用程序的工具。它使用多种智能技术对一个应用程序的内容和功能进行全面的清查。 Burp Spi…

基于Qt+FFmpeg的视频监控系统

github源码 需求分析 假设一个业务场景&#xff1a;每个员工工位旁有两个网络摄像头。老板需要一个员工监控软件&#xff0c;在上班时软件可以拉取RTSP视频流&#xff0c;也可以随时录制视频。这样老板就可以知道谁在摸鱼了 ◕‿◕ 为防有人上纲上线&#xff0c;在此特别声明…

【Redis】聊一下缓存双写一致性

缓存虽然可以提高查询数据的的性能&#xff0c;但是在缓存和数据 进行更新的时候 其实会出现数据不一致现象&#xff0c;而这个不一致其实可能会给业务来带一定影响。无论是Redis 分布式缓存还是其他的缓存机制都面临这样的问题。 数据不一致是如何发生&#xff1f; 数据一致…

【c语言】文件复制原理

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

pg事务:事务ID

事务ID pg中每个事务都会分配事务ID&#xff0c;事务ID分为虚拟事务ID和持久化事务ID&#xff08;transactionID&#xff09;。pg的事务ID非常重要&#xff0c;是理解事务、数据可见性、事务ID回卷等等的重要知识点。 虚拟事务ID 只读事务不会分配事务ID&#xff0c;事务ID是…

【我的C++入门之旅】(下)

前言 参考前章内容【我的C入门之旅】(上) 目录 前言1.引用常引用传值、传引用效率比较引用和指针的区别 2.auto关键字使用场景 3.范围for 语法糖4.inline函数5.指针空值nullptr 1.引用 取别名&#xff0c;一块空间有多个名字或者说是一个变量有多个名字 比如&#xff1a;李逵&…

【LeetCode: 44. 通配符匹配 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Linux】线程详解之线程概念

前言 在我们的教材中&#xff0c;对线程给出以下的概念&#xff1a; 是进程内部的一个执行分支&#xff0c;在进程的内部运行&#xff0c;属于进程的一部分&#xff0c;比进程更加轻量化。 可能有的人看完之后都是懵的&#xff0c;什么叫在进程的内部运行&#xff0c;什么又是…