专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

国庆在家,从0手撸一个依赖任务加载框架(有源码)

  前言
  我收回标题上的话,从0手撸一个框架一点也不轻松,需要考虑的地方比较多,一些实现和细节值得商榷,是一个比较大的挑战,有不足的地方欢迎大佬们提供意见
  依赖任务加载
  平时我们常常会使用各种第三方框架,如mmkv、glide、leakcanary等优秀的第三方库,大多数第三方库需要初始化后才能使用,因此会出现下面的代码:privatevoidinit{
  mmkv。init(context);
  glide。init(context);
  leakcanary。init(context);
  。。。。。。
  }
  如果不想让任务的初始化阻塞主线程太久,我们可以考虑通过异步的方式加载任务,直到最后一个必要任务加载完毕,开始进行对应的操作。
  如果部分任务是依赖关系,如下图任务A依赖任务B,单纯异步的方式的方式显然不能满足述求。
  我们通常会想到的解决办法有三类:
  将任务B写进任务A的末尾
  监听任务A加载成功的回调函数执行任务B
  通过volatile关键字卡住加载流程
  这样确实能够解决依赖任务的加载问题,但如果任务的数量和依赖关系更复杂呢?
  那如果是这样,你要怎么去处理?
  显然是有一种更通用的方法来解决这种场景,也就是下面会讲到的有向无环图。
  有向无环图的拓扑排序
  上面的依赖关系可以看成一种有向无环图(DirectedAcyclicGraph,DAG),有向可以理解,表现的是任务的依赖关系,而无环是必要的,因为如果任务A和任务B相互依赖,都需要等待对方的结束来开始,经典死锁套娃。
  我们可以通过拓扑排序将最后的线性执行关系呈现出来,什么是拓扑排序?
  将上面复杂依赖任务简单的分析一下,任务A前方没有依赖,因此我们可以将任务A的度记为0,任务B、C、E前方各有一个依赖关系,我们把度记为1,剩下的任务D前方由于有两个依赖关系,我们将度计为2;用一个任务队列储存度为0的任务,每当入列任务加载完毕,它对应依赖任务的度1,新的度为0的任务进队列。
  A入队列,A任务完成后,依赖A任务的任务B、C度1。
  这时任务B、C度都为0,都可以入队列,没有既定的顺序,我们选择入任务C,待C任务完成后,依赖C任务的D任务的度1。
  接着是任务B进去,B任务完成后,任务D、E的度1。
  最后是任务D、E其中的一个进去,随意选择,我们选择任务D。
  最后一个任务E。
  不考虑各个任务之间的耗时情况,依赖任务关系被拓扑排序成ACBDE,是不是发现依赖关系被解开了,排成了线性关系,这种将有向无环图拓扑成线性关系的方式被称为拓扑排序,拓扑结果根据所使用算法的不同而有所差异,这也是后面实现依赖任务加载框架的中心思想。
  手撸依赖任务加载框架
  定义IDAGTask类
  上面提到依赖任务的加载可以通过有向无环图的拓扑排序解决,我们开始用代码实现,先定义一个IDAGTask类:publicclassIDAGTask{
  }
  可能大家会疑问,为什么不用接口或者抽象类的思想去做这个基础类,后面解答这个疑惑。
  特殊的任务会存在加载线程限制,比如只能在主线程对这个任务进行加载,因此我们需要考虑这个任务是否可以同步。异步任务显然需要使用到线程池,定义IDAGTask类实现Runnable接口,方便后续丢进线程池。
  除此之外,之前讲到拓扑排序中任务有个度的概念,其实就是依赖关系的数量,在并发环境下为了保证依赖关系数量的线程可见性,这里我们使用AtomicInteger变量,通过CAS锁来保证依赖数量的实时正确性,因此IDAGTask类变成了这样:publicclassIDAGTaskimplementsRunnable{
  privatefinalbooleanmIsSyn;
  privatefinalAtomicIntegermAtomicInteger;
  booleangetIsAsync{
  returnmIsSyn;
  }
  voidaddRely{
  mAtomicInteger。incrementAndGet;
  }
  voiddeleteRely{
  mAtomicInteger。decrementAndGet;
  }
  intgetRely{
  returnmAtomicInteger。get;
  }
  Override
  publicvoidrun{
  }
  }
  回到之前为什么不用接口或者抽象类的方式来实现这个基础类,一方面为了后续将任务丢进线程池,IDAGTask实现了Runnable接口,接口的方式显然pass,另一方面抽象类的方式涉及到了另一个问题:
  抽象run方法,可以将IDAGTask任务的监听封装进去,比如startTask、completeDAGTask,如果我们继承IDATask,只需要将初始化部分单纯写进run方法就好了,非常优雅,但是有一种case,如果这个任务的初始化是用多线程实现的,我们调用完Task。init,马上执行completeDAGTask的监听其实是不对的
  基于上面的case,我选择了一种不优雅的实现方式,将startTask的监听写在run方法的开头,completeDAGTask的监听需要调用者自己添加,任务初始化是单线程实现写在run方法的末尾即可,任务初始化采用多线程实现,需要将completeDAGTask监听写进加载成功回调
  综上,run方法写进了startTask的回调,因此抽象失败,那么IDAGTask没有抽象方法,自然也不需要作为一个抽象类
  经过一些加工,最后IDATask实现如下:publicclassIDAGTaskimplementsRunnable{
  privatefinalbooleanmIsSyn;
  privatefinalAtomicIntegermAtomicInteger;
  privateIDAGCallBackmDAGCallBack;
  privatefinalSetmNextTaskSet;
  publicIDAGTask{
  this();
  }
  publicIDAGTask(booleanisSyn){
  this(,isSyn);
  }
  publicIDAGTask(Stringalias){
  this(alias,false);
  }
  publicIDAGTask(Stringalias,booleanIsSyn){
  mIsSynIsSyn;
  mAtomicIntegernewAtomicInteger;
  mDAGCallBacknewDAGCallBack(alias);
  mNextTaskSetnewHashSet;
  }
  booleangetIsAsync{
  returnmIsSyn;
  }
  voidaddRely{
  mAtomicInteger。incrementAndGet;
  }
  voiddeleteRely{
  mAtomicInteger。decrementAndGet;
  }
  intgetRely{
  returnmAtomicInteger。get;
  }
  voidaddNextDAGTask(IDAGTaskDAGTask){
  mNextTaskSet。add(DAGTask);
  }
  publicvoidsetDAGCallBack(IDAGCallBackDAGCallBack){
  this。mDAGCallBackDAGCallBack;
  }
  publicvoidcompleteDAGTask{
  for(IDAGTaskDAGTask:mNextTaskSet){
  DAGTask。deleteRely;
  }
  mDAGCallBack。onCompleteDAGTask;
  }
  Override
  publicvoidrun{
  mDAGCallBack。onStartDAGTask;
  }
  }
  定义DAGProject类
  IDAGTask的模板就被敲定了,接下来我们需要建立任务之间的关系:
  Set储存所有的任务,通过addDAGTask添加任务
  MapIDAGTask,Set储存所有的任务与其前置依赖关系,通过addDAGEdge添加任务依赖关系
  整体采用建造者模式构建DAGProject类
  于是DAGProject实现如下:publicclassDAGProject{
  privatefinalSetmTaskSet;
  privatefinalMapIDAGTask,SetmTaskMap;
  publicDAGProject(Builderbuilder){
  mTaskSetbuilder。mTaskSet;
  mTaskMapbuilder。mTaskMap;
  }
  SetgetDAGTaskSet{
  returnmTaskSet;
  }
  MapIDAGTask,SetgetDAGTaskMap{
  returnmTaskMap;
  }
  publicstaticclassBuilder{
  privatefinalSetmTaskSetnewHashSet;
  privatefinalMapIDAGTask,SetmTaskMapnewHashMap;
  publicBuilderaddDAGTask(IDAGTaskDAGTask){
  if(this。mTaskSet。contains(DAGTask)){
  thrownewIllegalArgumentException;
  }
  this。mTaskSet。add(DAGTask);
  returnthis;
  }
  publicBuilderaddDAGEdge(IDAGTaskDAGTask,IDAGTaskpreDAGTask){
  if(!this。mTaskSet。contains(DAGTask)!this。mTaskSet。contains(preDAGTask)){
  thrownewIllegalArgumentException;
  }
  SetpreDAGTaskSetthis。mTaskMap。get(DAGTask);
  if(preDAGTaskSet){
  preDAGTaskSetnewHashSet;
  this。mTaskMap。put(DAGTask,preDAGTaskSet);
  }
  if(preDAGTaskSet。contains(preDAGTask)){
  thrownewIllegalArgumentException;
  }
  DAGTask。addRely;
  preDAGTaskSet。add(preDAGTask);
  preDAGTask。addNextDAGTask(DAGTask);
  returnthis;
  }
  publicDAGProjectbuilder{
  returnnewDAGProject(this);
  }
  }
  }
  使用时,我们需要创建对应的IDAGTask,通过addDAGTask、addDAGEdge方法构建出对应有向无环图:ATaskanewATask;
  BTaskbnewBTask;
  CTaskcnewCTask;
  DTaskdnewDTask;
  ETaskenewETask;
  DAGProjectdagProjectnewDAGProject。Builder
  。addDAGTask(a)
  。addDAGTask(b)
  。addDAGTask(c)
  。addDAGTask(e)
  。addDAGTask(d)
  。addDAGEdge(b,a)
  。addDAGEdge(c,a)
  。addDAGEdge(d,b)
  。addDAGEdge(d,c)
  。addDAGEdge(e,b)
  。builder;
  表达任务依赖关系的DAGProject对象就通过建造者模式构建成功了。
  依赖任务加载的调度
  当多个任务构建成有向无环图的DAGProject后,我们先不着急丢进线程池,执行对应逻辑前先检测是否有环,这样我们可以在任务加载前抛出相互依赖的错误,大可不必等到执行至有环那一步才抛出。虽然有环可以靠输入者去保障,但是在一些小细节方面,我们要求输入者保证过于苛刻也过于差体验。
  将度为0的任务储存在tempTaskQueue
  while循环将任务取出,存在依赖关系则对应的任务度1,如果度为0,添加到resultTaskQueue
  判断最后的resultTaskQueue与之前储存任务的set个数是否相等,false则有环抛出异常publicclassDAGScheduler{
  privatevoidcheckCircle(SetTaskSet,MapIDAGTask,SetTaskMap){
  LinkedListresultTaskQueuenewLinkedList;
  LinkedListtempTaskQueuenewLinkedList;
  for(IDAGTaskDAGTask:tempTaskSet){
  if(tempTaskMap。get(DAGTask)){
  tempTaskQueue。add(DAGTask);
  }
  }
  while(!tempTaskQueue。isEmpty){
  IDAGTasktempDAGTasktempTaskQueue。pop;
  resultTaskQueue。add(tempDAGTask);
  for(IDAGTaskDAGTask:tempTaskMap。keySet){
  SettempDAGSettempTaskMap。get(DAGTask);
  if(tempDAGSet!tempDAGSet。contains(tempDAGTask)){
  tempDAGSet。remove(tempDAGTask);
  if(tempDAGSet。size0){
  tempTaskQueue。add(DAGTask);
  }
  }
  }
  }
  if(resultTaskQueue。size!tempTaskSet。size){
  thrownewIllegalArgumentException(相互依赖,玩屁啊,我不跑了!);
  }
  }
  }pre
  检测完环后,开始调度这些依赖任务,将度为0的任务加入阻塞队列,通过newSingleThreadExecutor开启一个线程不断去阻塞队列拿任务。
  判断拿出的任务属于主线程执行还是异步执行,主线程执行通过handler。post发送出去,异步执行通过线程池execute
  所有任务执行完毕,关闭线程池,结束遍历
  不断将度为0的任务加入阻塞队列publicclassDAGScheduler{
  privatevoidloop{
  for(IDAGTaskDAGTask:mTaskSet){
  if(DAGTask。getRely0){
  mTaskBlockingDeque。add(DAGTask);
  }
  }
  ExecutorServicesingleThreadExecutorExecutors。newSingleThreadExecutor;
  singleThreadExecutor。execute({
  for(;;){
  try{
  while(!mTaskBlockingDeque。isEmpty){
  IDAGTaskexecutedDAGTsk(IDAGTask)mTaskBlockingDeque。take;
  if(executedDAGTsk。getIsAsync){
  HandlerhandlernewHandler(getMainLooper);
  handler。post(executedDAGTsk);
  }else{
  mTaskThreadPool。execute(executedDAGTsk);
  }
  mTaskSet。remove(executedDAGTsk);
  }
  if(mTaskSet。isEmpty){
  singleThreadExecutor。shutdown;
  mTaskThreadPool。shutdown;
  return;
  }
  IteratoriteratormTaskSet。iterator;
  while(iterator。hasNext){
  IDAGTaskDAGTaskiterator。next;
  if(DAGTask。getRely0){
  mTaskBlockingDeque。put(DAGTask);
  iterator。remove;
  }
  }
  }catch(InterruptedExceptione){
  e。printStackTrace;
  }
  }
  });
  }
  }
  至此依赖任务的调度器搭建完毕,配合之前构建好的DAGProject,使用方法如下:
  DAGSchedulerdagSchedulernewDAGScheduler;dagScheduler。start(dagProject);
  使用方式
  第一步,对应build。gradle配置远程依赖,已经发布到mavencentral,不用担心jcenter弃用。
  implementationwork。lingling。dagtask:dagtsk:1。0。0
  第二步,继承IDAGTask类,在run方法中实现对应的初始化逻辑。publicclassATaskextendsIDAGTask{
  publicATask(Stringalias){
  super(alias);
  }
  Override
  publicvoidrun{
  super。run;
  try{
  模拟随机时间
  RandomrandomnewRandom;
  Thread。sleep(random。nextInt(1000));
  }catch(InterruptedExceptione){
  e。printStackTrace;
  }
  第三方框架内部使用同步加载
  completeDAGTask方法写在run方法末尾即可
  completeDAGTask;
  }
  第三方框架内部使用异步加载
  completeDAGTask方法需要写进成功回调
  onLibrarySuccess{
  completeDAGTask;
  }
  }
  tips:加载任务内部未开线程,completeDAGTask方法写在run方法的末尾,感知初始化结束;加载任务内部使用多线程,需要将completeDAGTask方法写进加载成功回调。
  第三步,根据任务的依赖关系构建DAGProject并执行。
  回首一开始出现的复杂依赖关系:
  我们模拟对应的任务,任务A、B、C、D、E,构建DAGProject如下:ATaskanewATask(ATask);
  BTaskbnewBTask(BTask);
  CTaskcnewCTask(CTask);
  DTaskdnewDTask(DTask);
  ETaskenewETask(ETask);
  DAGProjectdagProjectnewDAGProject。Builder
  。addDAGTask(b)
  。addDAGTask(c)
  。addDAGTask(a)
  。addDAGTask(d)
  。addDAGTask(e)
  。addDAGEdge(b,a)
  。addDAGEdge(c,a)
  。addDAGEdge(d,b)
  。addDAGEdge(d,c)
  。addDAGEdge(e,b)
  。builder;
  DAGSchedulerdagSchedulernewDAGScheduler;
  dagScheduler。start(dagProject);
  依赖任务执行结果如下:
  可以看到依赖任务被拆开成A、C、B、E、D的顺序进行执行。
  结语
  行文至此,总算凑到了结尾,1202年了,居然还有人在用java写客户端。
  框架实现整体很简单,但还是踩了很多坑,大到框架整体应该如何实现,小到设计模式应该如何使用、对外应该暴露什么方法、mavencentral如何上传等等各种细节问题,综上,这是一篇很青涩的文章。中途参考了很多大佬的文章思路和美好意见,但还是很不足,欢迎大佬们下场oneone指导。
  最后贴一下github链接:
  https:github。comLING0001DAGTask

岳家军二代将领有十九人,出众的猛将只有四人,实力还胜过岳飞在说岳前期,岳飞抗金平贼寇,也收服了一大批的猛将。岳家军初代将领有着五十多人。这些人中大多都是与岳飞同辈的,而且都是结拜兄弟,有着杨再兴何元庆余化龙伍尚志罗延庆王贵张显汤怀牛皋施全中国各省名字的由来你知道吗?下首先我们来说陕西省陕西之名,起于西周,陕西以陕源之西而得名,是指现在的河南省陕县西南,陕漠以西的地区,简称陕。安徽建省于清朝康熙六年(公元1667年),省名取当时安庆徽州两府首字合揭秘戈尔巴乔夫时期的新闻公开性改革苏联解体对于西方来说是一场胜利,但对于厉害切身的苏联人民来说却是一场灾难。关于苏联解体的原因有很多,其中新闻公开性改革扮演了至关重要的角色。自1985年3月戈尔巴乔夫入主克里姆林宫春秋小霸王是怎么炼成的,且看郑国的崛起周幽王死后,西周既灭,周室东迁,正式进入了春秋时期。而在春秋开局中第一个混的有声有色的诸侯就是郑国,也叫春秋小霸王。郑国三公雕像郑国的开国之君是郑桓公郑伯友,郑伯友是周厉王的儿子,诸葛亮临死前,阿斗问了他个问题,诸葛亮才明白他一直在装傻现在我们常用扶不起的阿斗来形容一个人的无能。众所周知,阿斗实际上是刘备的儿子刘禅。在众人眼里,刘禅是个傻子,但历史真的如此吗?诸葛亮死后的30年,刘禅依然坐稳自己的皇位,在那个战乱闯王李自成被杀后,他的妻子高桂英是什么结局?说出来你可能不信提起闯王李自成,你可能一下子就想到历史教科书上那个在别人仰慕中的跨刀英雄画像,自信威武。提起闯王李自成,你可能会想起来是他推翻了大明王朝,逼得崇祯皇帝自挂煤山东南枝。提起闯王李自成古代没有牙刷牙膏,他们怎么清洁牙齿呢?很多人认为古人没有牙刷和牙膏,肯定不刷牙,嘴里的味道会很大很难闻,如果不刷牙,那简直是行走的沼气池,烟灰缸。其中龌龊这词在古代,专门用来形容牙齿肮脏不洁的是一种病态。古人认为牙不行河北省的省会,总计迁移了11次,为何选择了石家庄市?在之前的文章中,作者和大家聊了一系列关于我国河北省与周边各省区划调整的故事,例如河北省与山西省河北省与河南省之间的区划变动。今天,我们聊聊河北省的省会变迁。从清朝以来,河北省的省会八水绕长安之浐河西安(古称长安)是十三朝古都,世界四大古都,地处关中平原中部,北临渭河,南依秦岭,是华夏文明的发源地之一。西安历史悠久,文化底蕴深厚,人文古迹和历史故事不胜枚举。自古水与人类文明密鲁迅当时的工资是多少?根据鲁迅的日记,1912年到1926年期间,鲁迅的工资几经上涨,最后每月的月薪是360元,虽然据说民国政府经常拖欠工资,导致鲁迅的工资只有200左右,但当时官方给鲁迅的工资真的是3一边读史,一边感悟之说不尽的南北朝02刘裕的野望这一小节,其实就是着重讲了三个与刘裕相关并且带有一点神话色彩的故事。先简单来说一下这三个故事一次,刘裕从健康回京口,在一客栈休息,并向客栈大娘讨酒喝。大娘说道让他去后室自己取酒喝。
泡茶还在直接倒热水?难怪不好喝,3个技巧让你的茶甘醇可口茶叶可以说是最常见的一种饮品。在中国,爱喝茶的人几乎是随处可见,甚至喝茶已经成为了很多人生活中的一部分。不过爱喝茶不代表会泡茶,这时候就有人反驳了泡茶有什么难的,不就是放些茶叶,倒双十一数码好物推荐攻略三大品牌全家桶值得关注,互联体验拉满双十一目前已经提前开启预售活动了,相信不少小伙伴们已经提前看好了,当然也会有朋友举棋不定。而每一年的双十一也是入手智能数码产品的高峰期,因为有着实打实的优惠,基本不会存在先提价后降创新赋能生态,领筑科技荣获消防科技创新品牌荣誉称号2022年10月26日,以发展聚焦赋能为主题的CFIC2022中国消防安全产业大会暨2022年(第十五届)消防行业品牌盛会在广州隆重举行。本次大会由特邀报告主题报告品牌颁奖盛典高峰高端芯片战局激烈三星1。4nm制程2027年投产本报记者谭伦北京报道高端芯片制程之战愈演愈烈。在近日加州举行的代工论坛上,三星公布了其芯片制造业务的最新路线图。三星电子晶圆代工事业执行副总裁康文秀表示,公司将在2025年开始大规适合老年人使用的智能手机,预算2千元以内看这5款,流畅使用3年您在阅读前请点击上面的关注二字,后续会第一时间为您提供更多有价值的相关内容,感谢您的支持。现在很多老年人也已经开始使用智能手机,在双11期间如果要给老年人购买智能手机的话,预算在2被判有罪还能当董事长?三星任命李在镕担任最高职位韩国电子巨头三星任命被定罪的继承人事实上的老板李在镕为执行董事长。这一具有象征意义的举动意味着,这家全球最大的智能手机制造商现在将正式由其创始家族的第三代人执掌。李在镕于2017年美股收涨苹果涨超7,迅雷逆市大跌15。08中新经纬10月29日电美东时间周五,美国三大股指全线收涨。Wind截图截至收盘,道指涨2。59报32861。8点,标普500指数涨2。46报3901。06点,纳指涨2。87报111伊藤美诚承认与国乒存在很大差距,樊振东状态堪忧,解释输球原因10月28日,世界乒乓球职业大联盟(WTT)世界杯决赛在河南省新乡市继续进行。在一场女单焦点战中,王曼昱状态火热,以3比0击败了日本名将伊藤美诚,轻松晋级女单四强。赛后,王曼昱表示台媒台轻型商用车销量大增据台湾经济日报10月27日报道,岛内轻型商用车销量大增。近几年休旅车一度是台湾地区车市增长幅度最大的车型,但由于今年主力车款RAV4缺货,休旅车市场份额降低,商用车反而异军突起,是央视直播!李春江和马布里狭路相逢,上海对阵北控看点十足问如何每天都能收到如此有趣的体育原创资讯?答只需轻点右上角的关注按钮就能实现梦想。CBA常规赛第一阶段收官之战中,除了广东对阵浙江之外,还有一场备受球迷关注的比赛,那就是北控与上海双十一3000预算该买什么手机,这三款堪称不后悔系列3000预算可以买到的手机比较多了,在这里给大家推荐三款高性价比产品,购买时可作为参考。第一款iQOONeo7iQOONeo7的屏幕采用了发烧友们最爱的三星E5柔性直屏。这块屏幕不
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网