写点什么

【程序大侠传】全局变量与并发之战

作者:Disaster
  • 2024-07-26
    上海
  • 本文字数:4008 字

    阅读完需:约 13 分钟

【程序大侠传】全局变量与并发之战

前序

在编程的武侠世界中,开发人员除了接任务,还可以通过不断修炼提升自己的开发功力。以下是一些提升开发技能的秘籍:


  • 勤练基本功:

  • 熟练掌握编程语言:如同练习剑法,熟悉掌握一门或多门编程语言是基础。

  • 数据结构与算法:如同内功心法,掌握数据结构与算法能够大大提升解决问题的效率。

  • 研读经典:

  • 阅读优秀代码:阅读开源项目或优秀代码,如同研读武林秘籍,能够学习到很多编程技巧和设计思想。

  • 技术书籍和文档:阅读技术书籍和官方文档,深入理解技术原理和最佳实践。

  • 实战演练:

  • 参与项目:参与实际项目开发,通过实战积累经验,提升解决问题的能力。

  • 开源贡献:参与开源项目,接受社区的代码审查和反馈,提升代码质量和合作能力。

  • 切磋交流:

  • 技术讨论:与同事和社区成员进行技术讨论,分享经验和观点,碰撞出新的灵感。

  • 技术会议和培训:参加技术会议、培训和讲座,学习最新的技术趋势和实践。

  • 总结反思:

  • 代码复盘:定期回顾和反思自己的代码,找出不足并加以改进。

  • 技术博客:撰写技术博客,记录学习心得和实践经验,分享给更多人。

  • 保持好奇心:

  • 探索新技术:保持对新技术的好奇心,持续学习和尝试新的工具和方法。

  • 解决挑战:勇于面对和解决各种技术挑战,不断突破自己的技术瓶颈。


阿强平时除了接任务,其他时间都在孜孜不倦地修炼中。紧接上回,阿强刚处理完天机楼的发布的任务,看着自己的积分有不少的增加,离自己的近期目标越来越近,嘴角不自觉地上扬。紧绷的身体也开始变得松弛起来,随着心态跟身体不断的变化,阿强的身上的气场也开始变化,不知不觉阿强进入一种“心流状态”的特殊状态。这种状态修炼内功是非常合适的,修炼效果能够达到平时的 2~3 倍。阿强感受自己身上的变化就迫不及待想跟好友“小钊”去分享,去找“小钊”主要还是想去跟“小钊”去讨论技术功法,这样能快速提高自己的技术水平。当阿强传送到“小钊”洞府,刚想传音跟“小钊”说一起讨论一个技术问题,就看到小钊脸色不好地走出洞府。阿强眉毛一挑,连忙走到小钊的跟前问他发生了什么,小钊看到阿强脸上挤出了一点笑容地回答说:“没事,之前做的一个任务有点问题,现在需要我重新去接手改一下”,阿强听完心里一动,连忙说:“啥问题,跟我说说,看我能不能帮上忙”。小钊听后也是脸上浮现一抹感激的表情地说:“没问题,先进我府上跟你细细道来.......”

第五章:全局变量与并发之战

2 小时后,阿强从小钊的洞府走出,此时站在洞府内的小钊一扫脸上阴霾,轻松地说:“阿强,这回你的状态比以前好多了,此次沟通对我帮助很大,下次有什么问题可以来找我”,阿强脸上一副“我是高手”的表情地回答:“小钊,今天的我强的可怕,你的问题可得是碰到了硬茬”。小钊听完也不恼,不过嘴上也不留情地说:“全身上下只有嘴最硬的男人”,而我们阿强听到这话可不能忍,正打算跟小钊喷一手垃圾话。但小钊一挥手就把洞府门关上,阿强看到这一手操作哪能不知道小钊的小心思,直接就开始传音炮轰小钊,不过阿强这段时间发送的传音都是对方未读。此时的阿强心里可不是滋味,不过拿小钊也没办法,毕竟是自己的好友,总不能直接破开洞府跟他干上一架,只能默默在心里吃了这个暗亏。心想着下回再在其他的地方找回此次在小钊这边受到的委屈。随即阿强就开始回自己洞府,在回自己洞府的路上,阿强心里开始回忆在小钊洞府沟通的事情,其实这次小钊的问题给阿强起了警示作用,让阿强知道编码修行道路上对于并发问题需要小心再小心。其中对于并发问题,并发问题在阿强所在世界中并不少见,可以说,每个任务都会有很大概率能碰到并发问题的处理。而针对这些并发问题,代码剑宗里面也有相应的书籍做详细的描述。阿强跟小钊沟通完之后,他觉得自己很有必要去温故一下书籍内容:


并发问题 1.竞争条件(Race Condition):情景:多个弟子同时去取山庄中的唯一一瓶稀有药草。由于他们没有协调好时间,导致多个弟子同时试图拿走药草,结果药草被损坏或丢失。类比:在并发编程中,多个线程同时访问和修改同一共享资源,可能导致数据不一致或错误的结果。


2.死锁(Deadlock):情景:弟子甲去取药草,需要钥匙 A,同时弟子乙去取武功秘籍,需要钥匙 B。甲持有钥匙 A,但需要钥匙 B 才能完成任务;乙持有钥匙 B,但需要钥匙 A。结果,他们相互等待,谁也无法完成任务。类比:在并发编程中,两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行,形成死锁。


3.饥饿(Starvation):情景:弟子丙负责守护山庄大门,但由于其他弟子总是优先完成自己的任务,导致丙一直得不到支援,无法完成自己的任务。类比:在并发编程中,某个线程长期得不到所需的资源,无法执行或完成任务,形成饥饿状态。


4.资源争用(Resource Contention):情景:多个弟子同时需要使用练功房,但练功房的空间有限,导致弟子们争相占用,互相干扰。类比:在并发编程中,多个线程同时竞争有限的资源,导致性能下降或资源争用问题。


解决方案在武侠世界中,解决这些问题需要制定门派规则和协调机制:


1.锁和同步(Locks and Synchronization):情景:山庄规定,弟子在使用稀有药草时必须先领取使用许可,只有持有许可的弟子才能使用药草,避免同时使用。类比:在编程中使用锁机制(如互斥锁、读写锁)来确保线程安全访问共享资源。


2.死锁检测和预防:情景:山庄制定规矩,所有钥匙按照固定顺序使用,避免弟子相互等待的情况。类比:在编程中采用死锁检测算法或资源有序分配策略,避免死锁。


3.优先级调度:情景:山庄长老根据任务的重要性和紧急程度,分配弟子的任务优先级,确保重要任务优先完成。类比:在并发编程中使用线程优先级调度,确保高优先级线程得到及时执行。


而小钊这次的碰到的问题则是并发问题中的竞争条件,而具体的代码内容则是:


//apollo动态配置private static volatile ApolloHolder<Map<String,List<UserConfigVO>>> apolloUserConfigHolder            = new ApolloFileJsonHolder("userConfig", new TypeReference<Map<String,List<UserConfigVO>>>() {});
public Map<String, UserConfigVO> getUserInfoValidateMapByScene(Long userId, String platform, String scene) { Map<String, UserConfigVO> res = new LinkedHashMap<>(); //apolloUserConfigHolder Map<String, List<UserConfigVO>> userInfoConfigMap = apolloUserConfigHolder.get(); List<UserConfigVO> configList = userInfoConfigMap.get(scene); if(CollectionUtils.isNotEmpty(configList)){ for (UserConfigVO config : configList) { BiFunction<Long, String, Boolean> pageFunction = pageProcessMap.get(config.getPage()); Boolean validate = pageFunction.apply(userId,platform); config.setValidateResult(validate); res.put(config.getPage(),config); } } return res; }
@Datapublic class UserConfigVO { //动态配置文件会配置此字段 private String page; //动态配置文件会配置此字段 private Integer activationProgress;
private Boolean validateResult;
}
复制代码


其中 userConfig 动态配置内容如下:


{  "firstInfo": [    {      "page": "card",      "activationProgress": "100"    },    {      "page": "auth",      "activationProgress": "100"    }  ] }
复制代码


上述的代码片段中,阿强最开始看的也是不明所以,但随着小钊的描述,阿强心里的调用链路开始生成:



从上面时序图中不难看出,getUserInfoValidateMapByScene 这个方法的返回中的 validateResult 字段会影响后续的流程执行。而 getUserInfoValidateMapByScene 方法中乍一看每次都会生成一个新的 map 去接收结果,但是随着阿强更仔细地观察代码后发现,map 中的引用对象是 Apollo 静态对象,也就是说针对所有线程来调用都会去 set 静态对象中的 validateResult 字段值,那假设一种场景,有一个线程 A 调用 getUserInfoValidateMapByScene 方法设置 Apollo 静态对象中的 validateResult 为 false,在 A 线程拿到 validateResult 执行逻辑之前,此时有一个线程 B 也来调用 getUserInfoValidateMapByScene 方法设置 Apollo 静态对象中的 validateResult 为 true,那么就会导致线程 A 地执行结果不符合预期。


阿强根小钊知道是并发问题中的竞争条件场景后,很快他们就想到了问题解决方案,那就是定义一个新的对象去接收:


public Map<String, UserConfigVO> getUserInfoValidateMapByScene(Long userId, String platform, String scene) {        Map<String, UserConfigVO> res = new LinkedHashMap<>();        //apolloUserConfigHolder        Map<String, List<UserConfigVO>> userInfoConfigMap = apolloUserConfigHolder.get();        List<UserConfigVO> configList = userInfoConfigMap.get(scene);        if(CollectionUtils.isNotEmpty(configList)){            for (UserConfigVO config : configList) {                BiFunction<Long, String, Boolean> pageFunction = pageProcessMap.get(config.getPage());                Boolean validate = pageFunction.apply(userId,platform);                UserConfigVO userConfigVO = new UsersConfigVO();                userConfigVO.setValidateResult(validate);                userConfigVO.setPage(config.getPage());                userConfigVO.setActivationProgress(config.getActivationProgress());                res.put(userConfigVO.getPage(),userConfigVO);            }        }        return res;    }
复制代码


这种改法是将共享变量给改成线程独享,这样每次请求 getUserInfoValidateMapByScene 方法都会使用栈里面的对象也就不会有共享变量出现,也就打破了出现并发问题的前置条件。

发布于: 25 分钟前阅读数: 6
用户头像

Disaster

关注

talk is cheap,show me the code 2021-12-29 加入

A coder who likes open source, has worked in the field of network security and Android, and is now constantly exploring ing in the field of java

评论

发布
暂无评论
【程序大侠传】全局变量与并发之战_Disaster_InfoQ写作社区