线程开的越多就越好吗|趣谈线程池
前言
线程优化一直是启动优化中的一个必不可少的项目。作为一个 Android 程序员,你肯定希望应用启动的时候,火力全开,线程池拉满,每一个 CPU 核心满载而行。
可你把线程池拉满的时候,启动时长就一定会降低吗?
结果显然是否定的,之前我在进行启动优化的时候,就遇到了类似的问题。我引入了有向无环图类似的启动库后,又将线程池的数量设置为:
CPU核心数 * 2 + 1
看似没什么问题,后续启动时长居然还增长了一点点。
为什么会出现这样的问题?我们今天就好好聊聊。
一、做个实验
先做个实验,在应用启动过程中,主要做了两步:
- 主线程循环 10w 次,做一些简单的计算
- 线程池做一些异步任务,读取文件,然后将读取到的数据写入数据库,这个异步任务提交了 1000 次
核心线程数 = 2 * CPU 核心数 + 1
,变量最大线程数:
- 实验一:最大线程数 =
2 * CPU 核心数 + 1
- 实验二:最大线程数 = Int.MAX_VALUE
在模拟器上,实验二平均启动时长 6505 ms,实验一平均启动市场 5521 ms,从这点看,线程开太多对主线程是有影响的。
二、基础知识
在启动流程中基础知识必不可少,从上往下讲就是线程、线程池、内核 和 CPU,这些知识都是老生常谈了。
1. 线程
线程是操作系统进行运算调度的最小单位,可以理解为它就是系统执行的任务。
作为任务,它会有各种状态:
- NEW(新建):新创建的线程,还没有启动
- RUNNABLE(可运行):可以运行的线程
- BLOCKED(阻塞):阻塞状态的线程
- WAITTING(等待):等待状态
- TIME_WAITTING(计时等待)
- TERMINATED(终止)
各种状态可以进行如下转换:
处于可运行状态的线程不一定处于运行中,如果 CPU 核心数 < 线程数量,在某个时间点,处于运行中的线程数量最多也只能等于 CPU 核心数。
除此以外,只有处于可运行状态的线程才有机会获取 CPU 的青睐,从而分到时间片,得以执行。
2. 线程池
线程池的知识都很熟悉了,简单了解一下。
2.1 核心线程
简单来说,我们想了解的部分就是线程池的核心线程和非核心线程:
- 核心线程:核心线程会一直存在
- 非核心线程:当非核心线程闲置超过指定的时间,就会被销毁
通过配置合适的核心线程数和非核心线程数可以帮助我们管理好线程,可以带来以下好处:
- 降低资源消耗:重复利用线程,降低资源消耗
- 提供响应速度:任务一来就执行
- 管理好线程资源:避免无节制的使用线程,引发性能问题
除此以外,在配置核心线程数和非核心线程数的时候,还需要根据业务场景,将 CPU 密集型和 I/O 密集型任务考虑进去。
2.2 任务划分
我们经常将任务分为 I/O密集型 和 CPU密集型 任务,那么这两种有什么区别呢?
I/O 密集型任务指的是该任务的大部分时间用来提交 I/O 请求或者等待 I/O 请求。这类任务常常运行很短暂的一会儿,然后进入阻塞状态,等待更多的 I/O 请求。常见的如数据库操作、网络操作、键盘事件、屏幕操作等。
CPU 密集型任务指的是任务的大部分代码用来执行代码。该类任务常常会一直运行并占用着 CPU,直到时间片用完。常见的如数据计算、无限循环等。
那线程数如何设置?我们下面再去讲。
3. 内核
哪个线程先运行?什么时间运行?运行多久?这些都是调度程序说了算!
3.1 调度程序
调度程序是一个内核子系统,它是多任务操作系统的基础。多任务操作系统就是能够同时并发地交互执行多个进程的操作系统。
即使是单核处理器,它也可以并发的处理多个任务,只不过在一个时间点,只有一个正在执行的任务。
就好比安卓开发小王,身背几个需求,被产品要求同一天上线,虽然也能够完成,但他在某个时间点,只能写一个需求,如果想一个时间点同时进行两个需求,那得加人,也就是我们通常说的双核处理器,这就具备了并行的能力。
3.2 抢占式和非抢占式
多任务操作系统可以分为两种类型:非抢占式多任务和抢占式多任务。
Android 使用的是抢占式多任务,在这种模式下,每个任务都会被分配到一定的时间用来执行,一旦时间片用完,就会自动切换到下一个任务,分配的时间我们称之为时间片。
还拿小王来举例,小王身背三个需求,每天的计划中,上午需求 A,下午需求 B,晚上需求C。到了下午,即使需求 A 没做完,也要去做需求 B,这样可以保证了每个需求每天都会有进度。
从启动的角度来说,我们肯定不希望主线程和子线程分得同样的时间片,这可能会让我们的应用看着很慢。
为了给主线程分得更长的时间片,每个进程都有一个 nice 值,它会影响时间片的分配,但我们改不了这个,我们能够处理的就是给线程设置优先级,Android 中线程的优先级从 -19 到 19,值越低代表优先级越高,分得的时间片也就越长。
3.3 线程多了会怎么分配
上面的这些东西看似和我们应用层开发没关系,实则不然。
比如线程数量多了以后,我们先拿小王举例:
原先小王手里有 5 个需求,每个 2 天工时,做完一个再做下一个,10天能搞定。
现经理要求他同时开发 5 个需求,保证 5 个需求每天都有进度,那可就麻烦了,先不算 10 天开发时间,还得加上如下时间:
- 每天切其他四个项目时间成本
- 思考时间:每次切到下一个项目,都会想上次开发到哪,上次的思路是什么
加上这些乱七八槽的,原来 10 天能搞定的东西,现在得变成 12 天。
线程多了,也会有这样的问题,每次切换时间片都是成本。另外,线程的闲置率会上升,像这样运行 14ms 要等 185 ms:
还拿小王来看,原先五个需求,桉顺序做,每个需求的生命周期就 2 天,但是并行开发后,每个需求的生命周期都拉长了,到了 12 天左右。对于启动的主线程来讲可不是好事!
理想的情况应该是量力而行,当小王开发一个需求遇到问题需要等产品回复而停滞,在等待的这段时间内,开发另外一个需求,知道产品回复完,再找一个合适的时间切回来,这样,反而会提升效率,将工作时间缩短到 9 天。
4. CPU
在2022年发布的 Android 低端机上,也都标配了 8 核心的 CPU,核心数越多,就意味着并行能力越强。
注意,这里用的是并行,而不是并发。
一个核心,就代表着团队只有一个开发,8 核代表着团队有八个开发,意味着一个时间点最高可以有8个需求同时进行。
二、线程数如何设置
上面说了那么多,大家最想知道的就是线程数如何设置。
一般而言,核心线程数和最大线程数都设置为 CPU核心数 * 2 +1 ,阻塞队列使用 LinkedBlockingDeque
。
1. 任务因素
但这个数字肯定不是绝对的,我们需要考虑到 CPU 密集型任务 和 IO 密集型任务的区别。
如果我们使用子线程都是处理网络、数据库、读文件等操作,这个数字就可以设置大一点;如果子线程仅执行一些耗时的计算代码,这个数字就可以设置小一点。
2. 任务闲置
即使我们自己设置的线程池没什么问题,但程序一启动,任务执行时候的线程闲置率一看就知道还有问题,比如这张图:
为什么会出现这种闲置率太高的情况,原因可能如下:
- 过多使用
New Thread
或者不节制的使用线程池 - 很多第三方 SDK 都使用自身的线程池或者线程
查看闲置率有两种,分别是使用Android Studio中的Profiler和Shell命令。
推荐大家使用 Profiler,好处可太多了:
- 可以查看线程总数
- 可以查看CPU的负载情况
- 可以查看每个任务的闲置率
- ...
直接使用 Profiler 中的 System TraceView 只能查看系统级别的方法,如果是我们想查看的方法,需要这么处理:
public void test{Trace.beginSection("名称");//... 代码省略Trace.endSection();
}
对每个方法做上述过程确实太麻烦,所以都是配合函数插桩使用。
另外一个就是使用 Shell 命令,我们可以在 Android Studio 中 Logcat 窗口看到应用的进程 Id,进入 adb shell 后,就可以通过输入命令 cat /proc/{进程ID}/schedstat
查看:
emulator64_x86_64_arm64:/ $ cat /proc/7775/schedstat
5511910111 2055599424 6712
// 参数一 CPU运行时间
// 参数二 该进程等待时间
// 参数三 主动切换和被动切换的次数
这些数据只能够我们查看大概的情况。
总结
关于线程我们能做的并不多,尽量去收敛线程:
- 禁止使用 New Thread 方式去创建线程
- 统一应用内线程池,并制定合适的核心线程和最大线程数量
- 编写公司库的时候,如需使用线程池,提供设置线程池的接口
- 可以设置自身线程池的第三方库,优先设置应用内线程池,比如 OkHttp
- Hook 第三方库使用
New Thread
,改为应用内线程池 - 能懒加载尽量懒加载第三方库,避免过早的竞争系统资源
主要就这些,如有不对的地方,评论区见~
本文由博客一文多发平台 OpenWrite 发布!
线程开的越多就越好吗|趣谈线程池相关推荐
- java线程的优先级是数字越大优先级越高_《深入理解Java虚拟机》5分钟速成:12章(Java内存模型与线程)...
第12章 Java内存模型与线程 前言: 1.物理机如何处理并发问题? 2.什么是Java内存模型? 3.原子性.可见性.有序性的具体含义和应用实现? 4.volatile 关键字特性? 5.基于vo ...
- 「杂谈」那些越早知道越好的人生经验
笔者结合自己的人生经历,以及发生在笔者周边的朋友身上的事情,谈谈那些越早知道越好的人生经验,希望能帮到年轻人! 1,上大学之前不要恋爱.大学之前,个人心理上不够成熟,在中学阶段谈恋爱,会让自己陷入迷茫 ...
- 社保,交得越多亏得越多(转)
以前对社保不是很了解,总以为是交得越多越好,因为你交得多,公司相应就交得更多.但是最近这两天仔细研究了一下社保政策,真是有点大吃一惊,发现完全不是这么回事,从某种意义上来说,反而是交得越多越亏. 以上 ...
- 为什么有三AI从来不追热点,信息越多学的越慢
周一又到了,大家又开始紧张的工作和学习.前面有三已经说过我们的作死三原则中的"不接广告","不转文章只做原创",今天来谈谈最后一个问题,"不追热点&q ...
- 上半年银行罚单不断,7月越早贷款越有利
刚过去的6月份,估计大家都感觉到了资金紧张的氛围,幸好央行连续投放了一些流动性,让6月这个关键的节点能够平稳渡过. 但是大家不要高兴得太早,虽然6月平稳渡过了,但是接下来的7月才是真正考验大家的时候, ...
- 越狠越有饭吃,越不狠越没饭吃
1.这个社会,你越狠越有饭吃,越不狠越没饭吃,这样执行了,我的合伙人就不会干扰我,我的决定会越来越理智,然后才有可能做得大,要不然,他老是要来影响我,这个生意还要不要做,钱没赚到,一肚子火. 就这些我 ...
- 男人三十而立,被动收入,越早开始越好
被动收入,越早开始越好 那些曾经困扰过你的问题和焦虑,如果你不正面冲撞它,击破它,它们就会在你30岁.35岁.40岁.45岁的时候,卷土重来.并且一次比一次凶狠,而你却一次比一次无力.那些曾经困扰过你 ...
- 千年疑惑:为什么我越累越失眠,越睡越疲惫?
来源:我是科学家iScientist 作者:是沈持盈 编辑:EON,Ent,李小葵 "这个方案还是不行,明天之前再改一版."我面前是老板那张平静又阴沉的脸. "可是--& ...
- [转载]教你在家轻松做麻酱烧饼——这烧饼酥的掉渣,越嚼还越香呀_万金油_新浪博客...
原文地址:教你在家轻松做麻酱烧饼--这烧饼酥的掉渣,越嚼还越香呀作者:哈尼22 秋季,北方的天气也是越来越冷了,每当这个时候就爱吃刚做好的热乎乎的烧饼了,每次吃完都好满足呀.所以这次就分享我做的次数最 ...
最新文章
- 3D 激光雷达地图相对精度自动评价算法
- 语言相关系数显著性_相关性分析在SPSS中的具体操作,一文读懂相关系数的含义及使用——【杏花开生物医药统计】...
- iOS MMDrawerController源码解读(一)
- python join_详解Python中的join()函数的用法
- Eclipse远程调试HDP源代码
- 每次请求都要建立连接吗?
- ssm上传文件获取路径_SSM实现图片上传下载功能
- echarts设置网格线颜色
- 从Java到Ruby——我的最近一次技术转型
- 数据分析师熬夜整理:最全「零售业」数据指标和使用技巧
- 小米平板4刷机win10或linux,小米平板2中Win10/MIUI系统互刷终极教程
- TOP Network技术总监Justin:TOP公链已率先实现多层状态分片
- iOS开发-代替UDID被弃用和UUID使用缺陷的解决方案
- 用RunASDate解决SAS 9.4许可证过期的问题
- 从零学前端第一讲:前端开发是什么?给初学者有什么建议?
- freeswitch命令一览表
- 信息学奥赛一本通 1367:查找二叉树(tree_a)
- Verilog -- 改进的Booth乘法(基4)
- AutoGluon包使用示例(表格、图像与多模态)
- 力扣HOT100算法题5:最长回文字串