前序
在编程的武侠世界中,开发人员除了接任务,还可以通过不断修炼提升自己的开发功力。以下是一些提升开发技能的秘籍:
- 勤练基本功:
- 熟练掌握编程语言:如同练习剑法,熟悉掌握一门或多门编程语言是基础。
- 数据结构与算法:如同内功心法,掌握数据结构与算法能够大大提升解决问题的效率。
- 研读经典:
- 阅读优秀代码:阅读开源项目或优秀代码,如同研读武林秘籍,能够学习到很多编程技巧和设计思想。
- 技术书籍和文档:阅读技术书籍和官方文档,深入理解技术原理和最佳实践。
- 实战演练:
- 参与项目:参与实际项目开发,通过实战积累经验,提升解决问题的能力。
- 开源贡献:参与开源项目,接受社区的代码审查和反馈,提升代码质量和合作能力。
- 切磋交流:
- 技术讨论:与同事和社区成员进行技术讨论,分享经验和观点,碰撞出新的灵感。
- 技术会议和培训:参加技术会议、培训和讲座,学习最新的技术趋势和实践。
- 总结反思:
- 代码复盘:定期回顾和反思自己的代码,找出不足并加以改进。
- 技术博客:撰写技术博客,记录学习心得和实践经验,分享给更多人。
- 保持好奇心:
- 探索新技术:保持对新技术的好奇心,持续学习和尝试新的工具和方法。
- 解决挑战:勇于面对和解决各种技术挑战,不断突破自己的技术瓶颈。
阿强平时除了接任务,其他时间都在孜孜不倦地修炼中。紧接上回,阿强刚处理完天机楼的发布的任务,看着自己的积分有不少的增加,离自己的近期目标越来越近,嘴角不自觉地上扬。紧绷的身体也开始变得松弛起来,随着心态跟身体不断的变化,阿强的身上的气场也开始变化,不知不觉阿强进入一种“心流状态”的特殊状态。这种状态修炼内功是非常合适的,修炼效果能够达到平时的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方法都会使用栈里面的对象也就不会有共享变量出现,也就打破了出现并发问题的前置条件。