编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.设计高效日志框架库
  • 02.国际化项目方案实践
  • 03.设计通用缓存框架
  • 04.设计轻量级线程池库
  • 05.设计通用轮训方案
  • 06.设计异步线程框架
  • 07.设计隐私合规检测库
  • /zh/android/plan/08.设备串口通信实践.html
  • 09.设计TTS语音实践
  • 10.设计悬浮窗开发实践
  • 13.SDK基础架构实践

04.设计轻量级线程池库

目录介绍

  • 01.整体概述说明
    • 1.1 项目背景
    • 1.2 遇到问题
    • 1.3 基础概念介绍
    • 1.4 设计目标
    • 1.5 产生收益分析
  • 02.线程池基础知识
    • 2.1 为何有线程池
    • 2.2 如何管理线程
    • 2.3 线程池创建
    • 2.4 线程池生命周期
    • 2.5 线程池弊端分析
    • 2.6 线程池方案对比
    • 2.7 优雅创建线程池
  • 03.线程分析
    • 3.1 线程为何有优先级
    • 3.2 线程池如何管理线程
    • 3.3 线程之间如何切换
    • 3.4 线程任务耗时统计
    • 3.5 线程数据共享
    • 3.6 多线程并发优化
    • 3.7 如何查看程序线程数
    • 3.8 将异步转为同步
  • 04.线程池封装思路
    • 4.1 封装总体思路
    • 4.2 主和子线程通信
    • 4.3 使用静态代理模式
    • 4.4 处理不同线程池
    • 4.5 阻塞队列的选择
    • 4.6 设置自定义Task
    • 4.7 线程池使用分析
  • 05.方案基础设计
    • 5.1 整体架构图
    • 5.2 UML设计图
    • 5.3 关键流程图
    • 5.4 接口设计图
    • 5.5 模块间依赖关系
  • 06.其他设计说明
    • 6.1 性能设计
    • 6.2 稳定性设计
    • 6.3 灰度设计
    • 6.4 降级设计
    • 6.5 异常设计
  • 07.其他说明介绍
    • 7.1 参考链接

01.整体概述

1.1 项目背景

1.2 遇到问题

  • 一般开启线程的操作如下所示
    new Thread(new Runnable() {
        @Override
        public void run() {
            //做一些任务
        }
    }).start();
    • 创建了一个线程并执行,它在任务结束后GC会自动回收该线程。在线程并发不多的程序中确实不错,而假如这个程序有很多地方需要开启大量线程来处理任务,那么如果还是用上述的方式去创建线程处理的话,那么将导致系统的性能表现的非常糟糕。
  • 主要的弊端有这些,可能总结并不全面
    • 大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
    • 大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿
    • 线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失

1.3 基础概念介绍

  • 线程池如何降低系统资源消耗?通过重用已存在的线程,降低线程创建和销毁造成的消耗,其核心就是线程的复用机制
  • 线程池提高系统响应速度?当有任务到达时,无需等待新线程的创建便能立即执行
  • 线程池如何管控并发数?线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
  • 线程池还有什么功能?线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

1.4 设计目标

  • 打造一个轻量级线程池工具,支持多种使用场景。比如执行核心任务,执行cpu类型任务,执行io类型任务,可以随意切换。
  • 简化handler处理消息,可以处理主线程或者子线程消息。同时支持使用HandlerThread处理大量消息逻辑,比如轮训操作。

1.5 产生收益分析

02.线程池基础知识

2.1 为何有线程池

  • 线程池好处:
    • 1)降低资源消耗;2)提高相应速度;3)提高线程的可管理性;4)可以提供定时定期可控线程数量等功能
  • 线程池的实现原理:
    • 当提交一个新任务到线程池时,判断核心线程池里的线程是否都在执行。如果不是,则创建一个新的线程执行任务。如果核心线程池的线程都在执行任务,则进入下个流程。
    • 判断工作队列是否已满。如果未满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
    • 判断线程池是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了,则交给饱和策略来处理这个任务。

2.2 如何管理线程

  • 大概的流程图如下
    • image
      image
  • 文字描述如下
    • ①如果在线程池中的线程数量没有达到核心的线程数量,这时候就回启动一个核心线程来执行任务。
    • ②如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行。
    • ③由于任务队列已满,无法将任务插入到任务队列中。这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会立即启动一个非核心线程来执行任务。
    • ④如果线程池中的数量达到了所规定的最大值,那么就会拒绝执行此任务,这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。

2.3 线程池创建

  • 通过Executors的工厂方法获取这五种线程池,其实它的内部还是通过new ThreadPoolExecutor(…)的方式创建线程池的,具体可以看看源码,这里省略呢……
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
  • 每种线程池大概的作用简单说一下
    • newFixedThreadPool(使用阻塞队列:LinkedBlockingQueue) 建立固定线程数量的线程池,每提交一个任务就会建立一个线程,直到达到线程池的最大大小。若是其中某个线程发生异常的话, 那么会有一个新的线程来替代它
    • newSingleThreadExecutor(使用阻塞队列:LinkedBlockingQueue) 建立单个线程的线程池,也就是只有一个线程,那么,全部提交上来的任务就是按顺序执行。若是这个线程发生异常的话,那么 会有一个新的线程来替代它
    • newCachedThreadPool(使用阻塞队列:SynchronousQueue) 建立一个大小不受限制的,可缓存的,线程数量自动扩容,自动销毁的线程池,若是线程池的大小超过了处理任务所须要的线程,那么线程池会回收部分线程(时间是60s)
    • newScheduledThreadPool (使用阻塞队列:DelayedWorkQueue) 建立一个大小无限的线程池,支持定时任务和周期性执行的任务

2.4 线程池生命周期

2.5 线程池弊端分析

  • 线程池如果使用不当也可以能造成代码缺陷
    • FixedThreadPool和SingleThreadPoolPool : 请求队列使用的是LinkedBlockingQueue,容许的请求队列的长度是Integer.MAX_VALUE,可能会堆积大量的请求,从而致使 OOM
    • CachedThreadPool和ScheduledThreadPool : 容许建立的线程数量是Integer.MAX_VALUE,可能会建立大量的线程池, 从而致使 OOM
  • 以上也就是建议本身配置线程池的缘由,那么线程池的那些参数究竟该如何配置? 线程池大小的配置:通常根据任务类型 和 CPU的核数(N)工具

2.8 线程池方案对比

  • 推荐ArchTaskExecutor;其次是RxJava;再其次是 AsyncTask

2.7 优雅创建线程池

  • 1、可以创建CPU密集型和io密集型的线程池。CPU密集型的线程池和核心数相关,核心线程数可以是2到4之间,最大线程数可以是2倍的核心数再加一。
  • 2、针对IO密集型的线程池,为例能够让越快执行,核心线程数可以为0,最大线程数可以是int的最大值。但是每个设备并不能创建这么多的线程,一旦线程数超限,仍然会发生崩溃。可以查看下设备的创建线程数的上限,/proc/sys/kernel/threads-max记录的设备能够创建线程的最大数,根据这个上限来设置最大的线程池。同时还可以创建一个备份的线程池,当线程用光后,启用备份的线程池,备份线程池可以只有一个或者多个线程。

03.线程分析

3.1 线程为何有优先级

3.2 线程池如何管理线程

3.3 线程之间如何切换

3.4 线程任务耗时统计

3.5 线程数据共享

3.6 多线程并发优化

3.7 如何查看程序线程数

3.8 将异步转为同步

  • 场景说明
    • 平时获取子线程返回结果以异步回调的方式获取返回到主线程,其实也可以通过某种方法转为同步返回。那么如何将这种异步回调改成同步式的回调?
  • 应用场景:
    • 1.单个线程处理结果返回到主线程;2.多个子线程并发请求,最终合并返回结果到主线程
  • 将异步转为同步
    • 第一种方式:使用sleep,直接在主线程睡眠,等待子线程返回。缺点:不明确子线程执行时间情况下,不一定能拿得到返回结果
    • 第二种方式:使用join,阻塞调用此方法的线程进入 TIMED_WAITING 状态,直到线程完成。
    • 第三种方式:使用Future,这是线程创建的又一种实现方式,只不过是阻塞异步的。
    • 第四种方式:使用CountDownLatch,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。
    • 第五种方式:使用同步屏障CyclicBarrier,线程间同步阻塞是使用的是ReentrantLock,可重入锁。
    • 第六种方式:使用信号量Semaphore,与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。
    • 第七种方式:使用移相器Phaser,重量级并发类,jdk7被引入,用来解决控制多个线程分阶段共同完成任务的情景问题。其作用相比CountDownLatch和CyclicBarrier更加灵活。
  • CountDownLatch和CyclicBarrier区别
    • CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
    • CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待,类似有福同享,有难同当
  • 如何将callback转为同步

04.线程池封装思路

4.1 封装总体思路

4.2 主和子线程通信

4.3 使用静态代理模式

  • 静态代理理解和应用:AbsTaskExecutor(抽象类) + DefaultTaskExecutor(委托类) + DelegateTaskExecutor(代理类)
  • AbsTaskExecutor,定义TaskExecutor的功能,即在主线程/IO线程执行任务。
  • DefaultTaskExecutor,ArchTaskExecutor的默认代理类,当client没有手动设置delegate时,使用其执行任务。
    • 使用了newFixedThreadPool的线程池模型。 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。源码设定最大并发数是4。postToMainThread时,mMainHandler初始化使用了双重效验锁。
  • DelegateTaskExecutor,实现了多个抽象方法,这里使用代理模式,实现后,执行全部转交给mDelegate,而此类也提供setDelegate的方法,允许client从外部提供一个TaskExecutor,这样设计的好处:
    • 1.通过代理,实际的执行模型(如:DefaultTaskExecutor的ExecutorService)交给代理类实现,隐藏了实现细节。
    • 2.给client比较高的自由度,客户端可以定义自己的TaskExecutor,从而定义实际的执行模型及队列等,也有利于方法埋点等操作。
    • 另外,通过getMainThreadExecutor与getMainThreadExecutor静态方法将sMainThreadExecutor与sIOThreadExecutor暴露给外部,而sMainThreadExecutor与sIOThreadExecutor相当于一个适配器,将ArchTaskExecutor的任务执行转换成标准的Executor,并未暴露mDelegate中的ExecutorService。

4.4 处理不同线程池

  • 处理io密集流
  • 处理比较核心的任务
  • 处理cpu密集任务

4.5 阻塞队列的选择

  • 1.ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • 2.LinkedBlockingQueue: 一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()和newSingleThreadExecutor使用了这个队列
  • 3.SynchronousQueue: 一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • 4.PriorityBlockingQueue: 一个具备优先级的无限阻塞队列。 五、DelayedWorkQueue 任务是按照目标执行时间进行排序。

4.6 设置自定义Task

  • AbsTaskExecutor使用代理模式实现了一个内部的线程池 DefaultTaskExecutor 提供使用,同时支持切换主线程的功能。其中如果使用者没有设置自己的 TaskExecutor ,那么 AbsTaskExecutor 将会使用 DefaultTaskExecutor 作为默认实现的线程池。
  • 后面如果需要用到线程池的地方,就不用再自己写一堆实现方法了,直接调用 AbsTaskExecutor 就行了,如果不满足要求,就自己再定义一个 TaskExecutor 。

4.7 线程池使用分析

其他

  • Android平台下的cpu利用率优化实现
  • https://juejin.cn/post/7253062314307223611#heading-4
贡献者: yangchong211
上一篇
03.设计通用缓存框架
下一篇
05.设计通用轮训方案