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

前序

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

  • 勤练基本功:
    • 熟练掌握编程语言:如同练习剑法,熟悉掌握一门或多门编程语言是基础。
    • 数据结构与算法:如同内功心法,掌握数据结构与算法能够大大提升解决问题的效率。
  • 研读经典:
    • 阅读优秀代码:阅读开源项目或优秀代码,如同研读武林秘籍,能够学习到很多编程技巧和设计思想。
    • 技术书籍和文档:阅读技术书籍和官方文档,深入理解技术原理和最佳实践。
  • 实战演练:
    • 参与项目:参与实际项目开发,通过实战积累经验,提升解决问题的能力。
    • 开源贡献:参与开源项目,接受社区的代码审查和反馈,提升代码质量和合作能力。
  • 切磋交流:
    • 技术讨论:与同事和社区成员进行技术讨论,分享经验和观点,碰撞出新的灵感。
    • 技术会议和培训:参加技术会议、培训和讲座,学习最新的技术趋势和实践。
  • 总结反思:
    • 代码复盘:定期回顾和反思自己的代码,找出不足并加以改进。
    • 技术博客:撰写技术博客,记录学习心得和实践经验,分享给更多人。
  • 保持好奇心:
    • 探索新技术:保持对新技术的好奇心,持续学习和尝试新的工具和方法。
    • 解决挑战:勇于面对和解决各种技术挑战,不断突破自己的技术瓶颈。

阿强平时除了接任务,其他时间都在孜孜不倦地修炼中。紧接上回,阿强刚处理完天机楼的发布的任务,看着自己的积分有不少的增加,离自己的近期目标越来越近,嘴角不自觉地上扬。紧绷的身体也开始变得松弛起来,随着心态跟身体不断的变化,阿强的身上的气场也开始变化,不知不觉阿强进入一种“心流状态”的特殊状态。这种状态修炼内功是非常合适的,修炼效果能够达到平时的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;
    }

@Data
public 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方法都会使用栈里面的对象也就不会有共享变量出现,也就打破了出现并发问题的前置条件。

  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值