位置: IT常识 - 正文

扒一扒抖音是如何做线程优化的(抖音是如何成功的)

编辑:rootadmin
扒一扒抖音是如何做线程优化的 背景

推荐整理分享扒一扒抖音是如何做线程优化的(抖音是如何成功的),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:抖音是怎么创造的,抖音是什么样,抖音是什么样,抖音是怎样发明的,抖音是如何成功的,抖音是怎样发明的,抖音是什么样,抖音是什么样,内容如对您有帮助,希望把文章链接给更多的朋友!

最近在对一些大厂App进行研究学习,在对某音App进行研究时,发现其在线程方面做了一些优化工作,并且其解决的问题也是之前我在做线上卡顿优化时遇到的,因此对其具体实现方案做了深入分析。本文是对其相关源码的研究加上个人理解的一个小结。

问题创建线程卡顿

我们可以可以知道 start()函数底层涉及到一系列的操作,包括 栈内存空间分配、内核线程创建 等操作,这些操作在某些情况下可能出现长耗时现象,比如由于linux系统中,所有系统线程的创建在内核层是由一个专门的线程排队实现,那么是否可能由于队列较长同时内核调度出现问题而出现长耗时问题? 具体的原因因为没有在线下复现过此类问题,因此只能大胆猜测,不过在线上确实收集到一些case, 以下是线上收集到一个阻塞现场样本:

那么是不是不要直接在主线程创建其他线程,而是直接使用线程池调度任务就没有问题? 让我们看下 ThreadPoolExecutor.execute(Runnable command)的源码实现

从文档中可以知道,execute函数的执行在很多情况下会创建(JavaThread)线程,并且跟踪其内部实现后可以发现创建Java线程对象后,也会立即在当前线程执行start函数。

来看一下线上收集到的一个在主线程使用线程池调度任务依旧发生卡顿的现场。

线程数过多的问题

在ART虚拟机中,每创建一个线程都需要为其分配独立的Java栈空间,当Java层未显示设置栈空间大小时,native层会在 FixStackSize 函数会分配默认的栈空间大小.

从这个实现中,可以看出每个线程至少会占用1M的虚拟内存大小,而在32位系统上,由于每个进程可分配的用户用户空间虚拟内存大小只有3G,如果一个应用的线程数过多,而当进程虚拟内存空间不足时,创建线程的动作就可能导致OOM问题.

另一个问题是某些厂商的应用所能创建的线程数相比原生Android系统有更严格的限制,比如某些华为的机型限制了每个进程所能创建的线程数为500, 因此即使是64位机型,线程数不做控制也可能出现因为线程数过多导致的OOM问题。

优化思路线程收敛

首先在一个Android App中存在以下几种情况会使用到线程

通过 Thread类 直接创建使用线程通过 ThreadPoolExecutor 使用线程通过 ThreadTimer 使用线程通过 AsyncTask 使用线程通过 HandlerThread 使用线程

线程收敛的大致思路是, 我们会预先创建上述几个类的实现类,并在自己的实现类中做修改, 之后通过编译期的字节码修改,将App中上述使用线程的地方都替换为我们的实现类。

使用以上线程相关类一般有几种方式:

直接通过 new 原生类 创建相关实例继承原生类,之后在代码中 使用 new 指令创建自己的继承类实例

因此这里的替换包括:

修改类的继承关系,比如 将所有 继承 Thread类的地方,替换为 我们实现 的 PThread修改上述几种类直接创建实例的地方,比如将代码中存在 new ThreadPoolExecutor(…) 调用的地方替换为 我们实现的 PThreadPoolExecutor

通过字码码修改,将代码中所有使用线程的地方替换为我们的实现类后,就可以在我们的实现类做一些线程收敛的操作。

Thread类 线程收敛

在Java虚拟机中,每个Java Thread 都对应一个内核线程,并且线程的创建实际上是在调用 start()函数才开始创建的,那么我们其实可以修改start()函数的实现,将其任务调度到指定的一个线程池做执行, 示例代码如下

class ThreadProxy : Thread() { override fun start() { SuperThreadPoolExecutor.execute({ this@ThreadProxy.run() }, priority = priority) }}线程池 线程收敛

由于每个ThreadPoolExecutor实例内部都有独立的线程缓存池,不同ThreadPoolExecutor实例之间的缓存互不干扰,在一个大型App中可能存在非常多的线程池,所有的线程池加起来导致应用的最低线程数不容小视。

另外也因为线程池是独立的,线程的创建和回收也都是独立的,不能从整个App的任务角度来调度。举个例子: 比如A线程池因为空闲正在释放某个线程,同时B线程池确可能正因为可工作线程数不足正在创建线程,如果可以把所有的线程池合并成 一个统一的大线程池,就可以避免类似的场景。

核心的实现思路为:

首先将所有直接继承 ThreadPoolExecutor的类替换为 继承 ThreadPoolExecutorProxy,以及代码中所有new ThreadPoolExecutor(…)类 替换为 new ThreadPoolExecutorProxy(…)ThreadPoolExecutorProxy 持有一个 大线程池实例 BigThreadPool ,该线程池实例为应用中所有线程池共用,因此其核心线程数可以根据应用当前实际情况做调整,比如如果你的应用当前线程数平均是200,你可以将BigThreadPool 核心线程设置为150后,再观察其调度情况。在 ThreadPoolExecutorProxy 的 addWorker 函数中,将任务调度到 BigThreadPool中执行

AsyncTask 线程收敛

对于AsyncTask也可以用同样的方式实现,在execute1函数中调度到一个统一的线程池执行

public abstract class AsyncTaskProxy<Params,Progress,Result> extends AsyncTask<Params,Progress,Result>{ private static final Executor THREAD_POOL_EXECUTOR = new PThreadPoolExecutor(0,20, 3, TimeUnit.MILLISECONDS, new SynchronousQueue<>(),new DefaultThreadFactory("PThreadAsyncTask")); public static void execute(Runnable runnable){ THREAD_POOL_EXECUTOR.execute(runnable); } /** * TODO 使用插桩 将所有 execute 函数调用替换为 execute1 * @param params The parameters of the task. * @return This instance of AsyncTask. */ public AsyncTask<Params, Progress, Result> execute1(Params... params) { return executeOnExecutor(THREAD_POOL_EXECUTOR,params); }}Timer类

Timer类一般项目中使用的地方并不多,并且由于Timer一般对任务间隔准确性有比较高的要求,如果收敛到线程池执行,如果某些Timer类执行的task比较耗时,可能会影响原业务,因此暂不做收敛。

卡顿优化

针对在主线程执行线程创建可能会出现的阻塞问题,可以判断下当前线程,如果是主线程则调度到一个专门负责创建线程的线程进行工作。

private val asyncExecuteHandler by lazy { val worker = HandlerThread("asyncExecuteWorker") worker.start() return@lazy Handler(worker.looper) } fun execute(runnable: Runnable, priority: Int) { if (Looper.getMainLooper().thread == Thread.currentThread() && asyncExecute ){ //异步执行 asyncExecuteHandler.post { mExecutor.execute(runnable,priority) } }else{ mExecutor.execute(runnable, priority) } }32位系统线程栈空间优化扒一扒抖音是如何做线程优化的(抖音是如何成功的)

在问题分析中的环节中,我们已经知道 每个线程至少需要占用 1M的虚拟内存,而32位应用的虚拟内存空间又有限,如果希望在线程这里挤出一点虚拟内存空间来,其利用PLT hook需改了创建线程时的栈空间大小。

在Java层直接配置一个 负值,从而起到一样的效果

OOM了? 我还能再抢救下!

针对在创建线程时由于内存空间不足或线程数限制抛出的OOM问题,可以做一些兜底处理, 比如将任务调度到一个预先创建的线程池进行排队处理, 而这个线程池核心线程和最大线程是一致的 因此不会出现创建线程的动作,也就不会出现OOM异常了。

另外由于一个应用可能会存在非常多的线程池,每个线程池都会设置一些核心线程数,要知道默认情况下核心线程是不会被回收的,即使一直处于空闲状态,该特性是由线程池的 allowCoreThreadTimeOut控制。

该参数值可通过 allowCoreThreadTimeOut(value) 函数修改

从具体实现中可以看出,当value值和当前值不同 且 value 为true时 会触发 interruptIdleWorkers()函数, 在该函数中,会对空闲Worker 调用 interrupt来中断对应线程

因此当创建线程出现OOM时,可以尝试通过调用线程池的 allowCoreThreadTimeOut 来触发 interruptIdleWorkers 实现空闲线程的回收。 具体实现代码如下:

因此我们可以在每个线程池创建后,将这些线程池用弱引用队列保存起来,当线程start 或者某个线程池execute 出现OOM异常时,通过这种方式来实现线程回收。

线程定位

线程定位 主要是指在进行问题分析时,希望直接从线程名中定位到创建该线程的业务,关于此类优化的文章网上已经介绍的比较多了,基本实现是通过ASM 修改调用函数,将当前类的类名或类名+函数名作为兜底线程名设置。

字节码修改工具

前文讲了一些优化方式,其中涉及到一个必要的操作是进行字节码修改,这些需求可以概括为如下

替换类的继承关系,比如将 所有继承于 java.lang.Thread的类,替换为我们自己实现的 ProxyThread替换 new 指令的实例类型,比如将代码中 所有 new Thread(…) 的调用替换为 new ProxyThread(…)

针对这些通用的修改,没必要每次遇到类似需求时都 进行插件的单独开发,因此我将这种修改能力集成到 LanceX插件中,我们可以通过以下 注解方便实现上述功能。

替换 new 指令@Weaver@Group("threadOptimize")public class ThreadOptimize { @ReplaceNewInvoke(beforeType = "java.lang.Thread", afterType = "com.knightboost.lancetx.ProxyThread") public static void replaceNewThread(){ }}

这里的 beforeType表示原类型,afterType 表示替换后的类型,使用该插件在项目编译后,项目中的如下源码

会被自动替换为

替换类的继承关系@Weaver@Group("threadOptimize")public class ThreadOptimize { @ChangeClassExtends( beforeExtends = "java.lang.Thread", afterExtends = "com.knightboost.lancetx.ProxyThread" ) public void changeExtendThread(){};}

这里的beforeExtends表示 原继承父类,afterExtends表示修改后的继承父类,在项目编译后,如下源码

会被自动替换为

总结

本文主要介绍了有关线程的几个方面的优化

主线程创建线程耗时优化线程数收敛优化线程默认虚拟空间优化OOM优化

这些不同的优化手段需要根据项目的实际情况进行选择,比如主线程创建线程优化的实现方面比较简单、影响面也比较低,可以优先实施。 而线程数收敛需要涉及到字节码插桩、各种对象代理 复杂度会高一些,可以根据当前项目的实际线程数情况再考虑是否需要优化。

线程OOM问题主要出现在低端设备 或一些特定厂商的机型上,可能对于某些大厂的用户基数来说有一定的收益,如果你的App日活并没有那么大,这个优化的优先级也是较低的。

其实不管你是在做项目中,还是面试中,都会发现有一些性能优化的相关问题出现,我们一般采用的方法是发现问题→定位问题→解决问题,但有时可能有些问题的出现,第一时间想不起来解决方法或是面试时答不上来,这也就证明了你对这一块掌握的不是很熟练。为了帮助到大家快速熟练掌握性能优化的知识点,整理了《Android 性能优化》的核心笔记大家可以参考:https://qr18.cn/FVlo89

Android 性能优化核心笔记

包含内容有:启动优化、内存优化、启动优化速度、卡顿优化、布局优化、崩溃优化、应用启动全流程(源码深度解析)……等内容

本文链接地址:https://www.jiuchutong.com/zhishi/297841.html 转载请保留说明!

上一篇:web数据可视化(ECharts版)(web数据可视化(Echars版)实训)

下一篇:Vue的环境配置(vue 配置)

  • 微商软文也需要技巧吗?答案全都在这里(微商写软文)

    微商软文也需要技巧吗?答案全都在这里(微商写软文)

  • 国家反诈中心怎么开启app自检(国家反诈中心怎么登录)

    国家反诈中心怎么开启app自检(国家反诈中心怎么登录)

  • 闪送强制提现多久到账(闪送强制提现多少钱)

    闪送强制提现多久到账(闪送强制提现多少钱)

  • 华为手机一直响个不停怎么解决(华为手机一直响铃怎么关闭)

    华为手机一直响个不停怎么解决(华为手机一直响铃怎么关闭)

  • 伙伴云是什么平台(伙伴云好用吗)

    伙伴云是什么平台(伙伴云好用吗)

  • 微信群200人怎么增加到500(微信群200人怎么扫码进群)

    微信群200人怎么增加到500(微信群200人怎么扫码进群)

  • 荣耀30pro防水吗(荣耀80pro防水性能怎么样)

    荣耀30pro防水吗(荣耀80pro防水性能怎么样)

  • b站怎么设置自动播放下一个视频(b站怎么设置自动跳过片头片尾)

    b站怎么设置自动播放下一个视频(b站怎么设置自动跳过片头片尾)

  • 趣专享为什么提现不了(趣专享怎么联系客服)

    趣专享为什么提现不了(趣专享怎么联系客服)

  • 手机mic什么意思(手机MIC什么意思)

    手机mic什么意思(手机MIC什么意思)

  • 英特尔显卡和nvidia会冲突吗(英特尔显卡和nvidia显卡哪个好)

    英特尔显卡和nvidia会冲突吗(英特尔显卡和nvidia显卡哪个好)

  • 荣耀30s有红外遥控吗(荣耀30带红外遥控)

    荣耀30s有红外遥控吗(荣耀30带红外遥控)

  • 苹果手机可不可以下载两个微信(苹果手机可不可以登陆两个微信)

    苹果手机可不可以下载两个微信(苹果手机可不可以登陆两个微信)

  • 快手一只穿云箭多少人民币(快手一个穿云箭)

    快手一只穿云箭多少人民币(快手一个穿云箭)

  • 美团怎么删除账单记录(美团怎么删除账单详情)

    美团怎么删除账单记录(美团怎么删除账单详情)

  • i7cpu能超频吗(i74770k超频)

    i7cpu能超频吗(i74770k超频)

  • iPhoneXS出现白苹果开不了机是什么原因(iphonexsmax出现白苹果)

    iPhoneXS出现白苹果开不了机是什么原因(iphonexsmax出现白苹果)

  • 网络虚拟化功能最小单元是多少个(网络虚拟化功能特性)

    网络虚拟化功能最小单元是多少个(网络虚拟化功能特性)

  • 下载微信时解析包出现问题怎么解决

    下载微信时解析包出现问题怎么解决

  • 手机内屏进水了怎么办(手机内屏进水了可以充电吗)

    手机内屏进水了怎么办(手机内屏进水了可以充电吗)

  • 小米9pro怎么设置字体样式(小米9pro5g怎么设置)

    小米9pro怎么设置字体样式(小米9pro5g怎么设置)

  • iphone11怎么关机(iphone11怎么关机和开机)

    iphone11怎么关机(iphone11怎么关机和开机)

  • qqlive异地登录啥意思(qq频道异地登录)

    qqlive异地登录啥意思(qq频道异地登录)

  • 华为nova5语音助手(华为nova5z智能语音)

    华为nova5语音助手(华为nova5z智能语音)

  • 访问微信朋友圈会留下足迹吗(访问微信朋友圈好友铃声有记录吗)

    访问微信朋友圈会留下足迹吗(访问微信朋友圈好友铃声有记录吗)

  • 朋友圈可以设置只给一个人看吗(朋友圈可以设置指定的时间)

    朋友圈可以设置只给一个人看吗(朋友圈可以设置指定的时间)

  • 华为手机打电话背景怎么设置(华为手机打电话有动画怎么关闭)

    华为手机打电话背景怎么设置(华为手机打电话有动画怎么关闭)

  • 荣耀v20怎么设置锁屏计步(荣耀v20怎么设置指纹解锁)

    荣耀v20怎么设置锁屏计步(荣耀v20怎么设置指纹解锁)

  • Linux Kernel 4.5在3月15日发布最终版

    Linux Kernel 4.5在3月15日发布最终版

  • 保本理财增值税可以开票吗怎么开
  • 广告公司的税务
  • 交所得税用计提吗
  • 分期付款发票开具
  • 未出资到位的股东
  • 刚成立的公司没有营业额怎么报税
  • 个体户一季度利润28万用缴纳增值税吗
  • 个体工商户增值税申报操作流程
  • 停车费收入增值税申报表怎么填
  • 顺丰开专票有明细吗
  • 注册资本没缴足可以注销吗
  • 个人劳务费增值税如何缴纳
  • 单位班车费用是福利费吗
  • 保费 车船税
  • 企业临时账户是什么意思
  • 多交印花税申请怎么写
  • 电子发票对航天信息利空吗
  • 预缴增值税一般计税依据
  • 总资产报酬率计算举例
  • 免租期租金能否确认递延所得税吗?
  • 工程备用金金额怎么算
  • 卷烟批发企业批发卷烟是否缴纳消费税
  • 收到代收款怎么做分录
  • mac怎么查询是不是正品
  • 在windows7提供了一种什么技术
  • 总公司与分公司的账务处理
  • 物业专项维修资金可以退还吗
  • 车间购买办公用品
  • php 抓取别的网站的内容
  • 跨区域涉税事项报告表报验流程
  • 为员工租赁房屋产生的租赁费可以抵扣增值税和所得税吗
  • php require的用法
  • 预收账款调增应纳税所得额
  • vue onshow
  • vue实战技巧
  • threejs loader
  • 圣保罗大教堂是什么教
  • 静电现象什么意思
  • 技术服务转包涉税处理
  • 待抵扣进项税的限额是什么
  • 附有退回条件的销售商
  • thinkphp用户token
  • 审计招标费包含哪些内容
  • 销售房产收取预算费用
  • 增值税贷方余额是负数怎么办
  • 非三包退车能否退款
  • 资本性支出与资本性支出(基本建设)的区别
  • 已经抵扣的发票红字信息表怎么开
  • 金税盘维护费应该在那个表填写
  • 暂估原材料,材料成本如何冲
  • 预提成本和冲回成本金额不一致情况说明
  • 以前多计提的税款怎么办
  • 外贸出口转内销会计分录
  • 车子以旧换新还能贷款吗
  • 增值税减免税款计入什么科目
  • 困难补助属于兜底政策吗
  • 什么是增资扩股协议
  • sql server使用
  • sql导入和导出数据
  • centos虚拟机怎么用
  • Mysql5.7在Centos6中的安装方法
  • mysql 左链接 右链接
  • 准确配置
  • 如何快速提升花呗额度
  • 阿里云电脑系统
  • win7共享设置(详细图文步骤)
  • win8软件不兼容怎么办
  • windows svchost
  • win7旗舰版系统激活密钥
  • shell字符串操作
  • 用python做开发
  • bootstrap和css的关系
  • jquery选择器的作用
  • 动态表单的设计与实现
  • 电子税务局怎么添加开票员
  • A级纳税人和一般纳税人区别
  • 河南税务公众号缴费养老保险
  • 12366属于哪个部门
  • 盐城企退人员养老金调整新标准
  • 江西省国家税务局网站
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设