前序
在編程的武俠世界中,開發(fā)人員除了接任務(wù),還可以通過不斷修煉提升自己的開發(fā)功力。以下是一些提升開發(fā)技能的秘籍:
- 勤練基本功:
- 熟練掌握編程語言:如同練習(xí)劍法,熟悉掌握一門或多門編程語言是基礎(chǔ)。
- 數(shù)據(jù)結(jié)構(gòu)與算法:如同內(nèi)功心法,掌握數(shù)據(jù)結(jié)構(gòu)與算法能夠大大提升解決問題的效率。
- 研讀經(jīng)典:
- 閱讀優(yōu)秀代碼:閱讀開源項(xiàng)目或優(yōu)秀代碼,如同研讀武林秘籍,能夠?qū)W習(xí)到很多編程技巧和設(shè)計(jì)思想。
- 技術(shù)書籍和文檔:閱讀技術(shù)書籍和官方文檔,深入理解技術(shù)原理和最佳實(shí)踐。
- 實(shí)戰(zhàn)演練:
- 參與項(xiàng)目:參與實(shí)際項(xiàng)目開發(fā),通過實(shí)戰(zhàn)積累經(jīng)驗(yàn),提升解決問題的能力。
- 開源貢獻(xiàn):參與開源項(xiàng)目,接受社區(qū)的代碼審查和反饋,提升代碼質(zhì)量和合作能力。
- 切磋交流:
- 技術(shù)討論:與同事和社區(qū)成員進(jìn)行技術(shù)討論,分享經(jīng)驗(yàn)和觀點(diǎn),碰撞出新的靈感。
- 技術(shù)會議和培訓(xùn):參加技術(shù)會議、培訓(xùn)和講座,學(xué)習(xí)最新的技術(shù)趨勢和實(shí)踐。
- 總結(jié)反思:
- 代碼復(fù)盤:定期回顧和反思自己的代碼,找出不足并加以改進(jìn)。
- 技術(shù)博客:撰寫技術(shù)博客,記錄學(xué)習(xí)心得和實(shí)踐經(jīng)驗(yàn),分享給更多人。
- 保持好奇心:
- 探索新技術(shù):保持對新技術(shù)的好奇心,持續(xù)學(xué)習(xí)和嘗試新的工具和方法。
- 解決挑戰(zhàn):勇于面對和解決各種技術(shù)挑戰(zhàn),不斷突破自己的技術(shù)瓶頸。
阿強(qiáng)平時除了接任務(wù),其他時間都在孜孜不倦地修煉中。緊接上回,阿強(qiáng)剛處理完天機(jī)樓的發(fā)布的任務(wù),看著自己的積分有不少的增加,離自己的近期目標(biāo)越來越近,嘴角不自覺地上揚(yáng)。緊繃的身體也開始變得松弛起來,隨著心態(tài)跟身體不斷的變化,阿強(qiáng)的身上的氣場也開始變化,不知不覺阿強(qiáng)進(jìn)入一種“心流狀態(tài)”的特殊狀態(tài)。這種狀態(tài)修煉內(nèi)功是非常合適的,修煉效果能夠達(dá)到平時的2~3倍。阿強(qiáng)感受自己身上的變化就迫不及待想跟好友“小釗”去分享,去找“小釗”主要還是想去跟“小釗”去討論技術(shù)功法,這樣能快速提高自己的技術(shù)水平。當(dāng)阿強(qiáng)傳送到“小釗”洞府,剛想傳音跟“小釗”說一起討論一個技術(shù)問題,就看到小釗臉色不好地走出洞府。阿強(qiáng)眉毛一挑,連忙走到小釗的跟前問他發(fā)生了什么,小釗看到阿強(qiáng)臉上擠出了一點(diǎn)笑容地回答說:“沒事,之前做的一個任務(wù)有點(diǎn)問題,現(xiàn)在需要我重新去接手改一下”,阿強(qiáng)聽完心里一動,連忙說:“啥問題,跟我說說,看我能不能幫上忙”。小釗聽后也是臉上浮現(xiàn)一抹感激的表情地說:“沒問題,先進(jìn)我府上跟你細(xì)細(xì)道來.......”
第五章:全局變量與并發(fā)之戰(zhàn)
2小時后,阿強(qiáng)從小釗的洞府走出,此時站在洞府內(nèi)的小釗一掃臉上陰霾,輕松地說:“阿強(qiáng),這回你的狀態(tài)比以前好多了,此次溝通對我?guī)椭艽?,下次有什么問題可以來找我”,阿強(qiáng)臉上一副“我是高手”的表情地回答:“小釗,今天的我強(qiáng)的可怕,你的問題可得是碰到了硬茬”。小釗聽完也不惱,不過嘴上也不留情地說:“全身上下只有嘴最硬的男人”,而我們阿強(qiáng)聽到這話可不能忍,正打算跟小釗噴一手垃圾話。但小釗一揮手就把洞府門關(guān)上,阿強(qiáng)看到這一手操作哪能不知道小釗的小心思,直接就開始傳音炮轟小釗,不過阿強(qiáng)這段時間發(fā)送的傳音都是對方未讀。此時的阿強(qiáng)心里可不是滋味,不過拿小釗也沒辦法,畢竟是自己的好友,總不能直接破開洞府跟他干上一架,只能默默在心里吃了這個暗虧。心想著下回再在其他的地方找回此次在小釗這邊受到的委屈。隨即阿強(qiáng)就開始回自己洞府,在回自己洞府的路上,阿強(qiáng)心里開始回憶在小釗洞府溝通的事情,其實(shí)這次小釗的問題給阿強(qiáng)起了警示作用,讓阿強(qiáng)知道編碼修行道路上對于并發(fā)問題需要小心再小心。其中對于并發(fā)問題,并發(fā)問題在阿強(qiáng)所在世界中并不少見,可以說,每個任務(wù)都會有很大概率能碰到并發(fā)問題的處理。而針對這些并發(fā)問題,代碼劍宗里面也有相應(yīng)的書籍做詳細(xì)的描述。阿強(qiáng)跟小釗溝通完之后,他覺得自己很有必要去溫故一下書籍內(nèi)容:
并發(fā)問題
1.競爭條件(Race Condition):
情景:多個弟子同時去取山莊中的唯一一瓶稀有藥草。由于他們沒有協(xié)調(diào)好時間,導(dǎo)致多個弟子同時試圖拿走藥草,結(jié)果藥草被損壞或丟失。
類比:在并發(fā)編程中,多個線程同時訪問和修改同一共享資源,可能導(dǎo)致數(shù)據(jù)不一致或錯誤的結(jié)果。
2.死鎖(Deadlock):
情景:弟子甲去取藥草,需要鑰匙A,同時弟子乙去取武功秘籍,需要鑰匙B。甲持有鑰匙A,但需要鑰匙B才能完成任務(wù);乙持有鑰匙B,但需要鑰匙A。結(jié)果,他們相互等待,誰也無法完成任務(wù)。
類比:在并發(fā)編程中,兩個或多個線程相互等待對方持有的資源,導(dǎo)致所有線程都無法繼續(xù)執(zhí)行,形成死鎖。
3.饑餓(Starvation):
情景:弟子丙負(fù)責(zé)守護(hù)山莊大門,但由于其他弟子總是優(yōu)先完成自己的任務(wù),導(dǎo)致丙一直得不到支援,無法完成自己的任務(wù)。
類比:在并發(fā)編程中,某個線程長期得不到所需的資源,無法執(zhí)行或完成任務(wù),形成饑餓狀態(tài)。
4.資源爭用(Resource Contention):
情景:多個弟子同時需要使用練功房,但練功房的空間有限,導(dǎo)致弟子們爭相占用,互相干擾。
類比:在并發(fā)編程中,多個線程同時競爭有限的資源,導(dǎo)致性能下降或資源爭用問題。
解決方案
在武俠世界中,解決這些問題需要制定門派規(guī)則和協(xié)調(diào)機(jī)制:
1.鎖和同步(Locks and Synchronization):
情景:山莊規(guī)定,弟子在使用稀有藥草時必須先領(lǐng)取使用許可,只有持有許可的弟子才能使用藥草,避免同時使用。
類比:在編程中使用鎖機(jī)制(如互斥鎖、讀寫鎖)來確保線程安全訪問共享資源。
2.死鎖檢測和預(yù)防:
情景:山莊制定規(guī)矩,所有鑰匙按照固定順序使用,避免弟子相互等待的情況。
類比:在編程中采用死鎖檢測算法或資源有序分配策略,避免死鎖。
3.優(yōu)先級調(diào)度:
情景:山莊長老根據(jù)任務(wù)的重要性和緊急程度,分配弟子的任務(wù)優(yōu)先級,確保重要任務(wù)優(yōu)先完成。
類比:在并發(fā)編程中使用線程優(yōu)先級調(diào)度,確保高優(yōu)先級線程得到及時執(zhí)行。
而小釗這次的碰到的問題則是并發(fā)問題中的競爭條件,而具體的代碼內(nèi)容則是:
//apollo動態(tài)配置
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 {
//動態(tài)配置文件會配置此字段
private String page;
//動態(tài)配置文件會配置此字段
private Integer activationProgress;
private Boolean validateResult;
}
其中userConfig動態(tài)配置內(nèi)容如下:
{
"firstInfo": [
{
"page": "card",
"activationProgress": "100"
},
{
"page": "auth",
"activationProgress": "100"
}
]
}
上述的代碼片段中,阿強(qiáng)最開始看的也是不明所以,但隨著小釗的描述,阿強(qiáng)心里的調(diào)用鏈路開始生成:
[圖片上傳失敗...(image-3e7012-1721961926299)]
從上面時序圖中不難看出,getUserInfoValidateMapByScene這個方法的返回中的validateResult字段會影響后續(xù)的流程執(zhí)行。而getUserInfoValidateMapByScene方法中乍一看每次都會生成一個新的map去接收結(jié)果,但是隨著阿強(qiáng)更仔細(xì)地觀察代碼后發(fā)現(xiàn),map中的引用對象是Apollo靜態(tài)對象,也就是說針對所有線程來調(diào)用都會去set靜態(tài)對象中的validateResult字段值,那假設(shè)一種場景,有一個線程A調(diào)用getUserInfoValidateMapByScene方法設(shè)置Apollo靜態(tài)對象中的validateResult為false,在A線程拿到validateResult執(zhí)行邏輯之前,此時有一個線程B也來調(diào)用getUserInfoValidateMapByScene方法設(shè)置Apollo靜態(tài)對象中的validateResult為true,那么就會導(dǎo)致線程A地執(zhí)行結(jié)果不符合預(yù)期。
阿強(qiáng)根小釗知道是并發(fā)問題中的競爭條件場景后,很快他們就想到了問題解決方案,那就是定義一個新的對象去接收:
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;
}
這種改法是將共享變量給改成線程獨(dú)享,這樣每次請求getUserInfoValidateMapByScene方法都會使用棧里面的對象也就不會有共享變量出現(xiàn),也就打破了出現(xiàn)并發(fā)問題的前置條件。