www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 架構(gòu)師社區(qū)
[導讀]荒腔走板 大家好,我是why。 時間過的真是快,一周又要結(jié)束了。那么,你比上周更博學了嗎?先來一個簡短的荒腔走板,給冰冷的技術(shù)文注入一絲色彩。 上面這張圖是我之前在南五環(huán),路過南苑機場的時候拍的。 這是一架飛機在降落,拍的時候我一下就想起了李志的


震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


荒腔走板

大家好,我是why。


時間過的真是快,一周又要結(jié)束了。那么,你比上周更博學了嗎?先來一個簡短的荒腔走板,給冰冷的技術(shù)文注入一絲色彩。


上面這張圖是我之前在南五環(huán),路過南苑機場的時候拍的。


這是一架飛機在降落,拍的時候我一下就想起了李志的《天空之城》


飛機飛過天空,天空之城

落雨下的黃昏的我們

此刻我在異鄉(xiāng)的夜里

感覺著你忽明忽暗

我想回到過去,沉默著歡喜

天空之城在哭泣越來越明亮的你


這位南京李先生算不上一個名人,只是在一個小圈子里面比較出名。但是呢,總有一些綜藝會在節(jié)目里面未經(jīng)許可直接使用他的歌曲。


簡單來說就是被侵權(quán)了。他每次都會去維權(quán)。


有的時候會得到道歉,有的時候胳膊擰不過大腿。


李志維權(quán)的時候說過:我能做的其實挺少,但是每一次我試圖去做一些事情時,都會有朋友跟我說,這樣沒用的,你看還是這個死樣,所有人都很絕望,我當然也絕望,但是我始終想,能拯救一個是一個,能教育一個是一個,哪有什么事情一夜之間全改了,都要慢慢來。


現(xiàn)在他是一位 404 歌手了。


正如他之前說過的這樣:我就怕我哪一天不愛這個世界了,只愛我自己,過自己小日子,跟他們一樣,賺點錢,戴個面具。但是那一天真來的話我也沒辦法。


我記得網(wǎng)易云音樂里面他的歌下面有一條評論是這樣的:關(guān)于李志我不想說太多,反正我知道,你聽李志的歌的時候你想的那個人是不會和你在一起的。


我覺得不是這樣的,我聽李志的時候我沒有想起誰。我只是想到了過往的真實而用力的生活。


所以,這句話應該是:關(guān)于李志我不想說太多,反正我知道,你聽李志的歌的時候你想到的都是你正在經(jīng)歷的,或者懷念的而又回不去的生活。


總之逼哥牛逼。


好了,說回文章。


JDK BUG

這篇文章,聊一下我最近才知道的一個關(guān)于 JDK 8 的 BUG 吧。


首先說一下我是怎么發(fā)現(xiàn)這個 BUG 的呢?


大家都知道我對 Dubbo 有一定的關(guān)注,前段時間 Dubbo 2.7.7 發(fā)布后我看了它的更新點,就是下面這個網(wǎng)址:

https://github.com/apache/dubbo/releases/tag/dubbo-2.7.7


其中有 Bugfixex 這一部分:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

每一個我都去簡單的看了一下,其他的 Bugfixes 或多或少都和 Dubbo 框架有一定的關(guān)聯(lián)性。但是上面紅框框起來的部分完全就是 JDK 的 Bug 了。


所以可以單獨拎出來說。


這個 Bug 我也是看到了這個地方才知道的,但是研究的過程中我發(fā)現(xiàn),這個怎么說呢:我懷疑這根本就不是 Bug ,這就是 Doug Lea 老爺子在釣魚執(zhí)法。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

為什么這樣的說呢,大家看完本文就知道了。


Bug 穩(wěn)定復現(xiàn)

點擊 Dubbo 里面的鏈接,我們可以看到具體的描述就是一個鏈接:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


打開這個鏈接:

https://bugs.openjdk.java.net/browse/JDK-8062841

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


我們可以看到:這個 Bug 是位于大名鼎鼎的 concurrent 包里面的 computeIfAbsent 方法。


這個 Bug 在 JDK 9 里面被修復了,修復人是 Doug Lea。


而我們知道 ConcurrentHashMap 就是 Doug Lea 的大作,可以說是“誰污染誰治理”。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


要了解這個 Bug 是怎么回事,就必須先了解下面這個方法是干啥的:

java.util.concurrent.ConcurrentHashMap#computeIfAbsent

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


從這個方法的第二個入?yún)?mappingFunction 我們可以知道這是 JDK 8 之后提供的方法了。


該方法的含義是:當前 Map 中 key 對應的值不存在時,會調(diào)用 mappingFunction 函數(shù),并且將該函數(shù)的執(zhí)行結(jié)果(不為 null)作為該 key 的 value 返回。


比如下面這樣的:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


初始化一個 ConcurrentHashMap ,然后第一次去獲取 key 為 why 的 value,沒有獲取到,直接返回 null。


接著調(diào)用 computeIfAbsent 方法,獲取到 null 后調(diào)用 getValue 方法,將該方法的返回值和當前的 key 關(guān)聯(lián)起來。


所以,第二次獲取的時候拿到了 “why技術(shù)”。


其實上面的代碼的 17 行的返回值就是 “why技術(shù)”,只是我為了代碼演示,再去調(diào)用了一次 map.get() 方法。


知道這個方法干什么的,接下來就帶大家看看 Bug 是什么。


我們直接用這個問題里面給的測試用例,地址:

https://bugs.openjdk.java.net/secure/attachment/23985/Main.java

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

我只是在第 11 行和第 21 行加入了輸出語句:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

正常的情況下,我們希望方法正常結(jié)束,然后 map 里面是這樣的:{AaAa=42,BBBB=42}


但是你把這個代碼拿到本地去跑(需要 JDK 8 環(huán)境),你會發(fā)現(xiàn),這個方法永遠不會結(jié)束。因為它在進行死循環(huán)。


這就是 Bug。


提問的藝術(shù)

知道 Bug 了,按理來說就應該開始分析源碼,了解為啥出現(xiàn)了會出現(xiàn)這個 Bug。


但是我想先插播一小節(jié)提問的藝術(shù)。因為這個 Bug 就是一個活生生的示例呀。 


這個鏈接,我建議你打開看看,這里面還有 Doug Lea 老爺子的親自解答:

https://bugs.openjdk.java.net/browse/JDK-8062841


首先我們看提出問題的這個人對于問題的描述(可以先不用細看,反正看著也是懵逼的):

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

通常情況下,被提問的人分為兩類人:


  1. 遇到過并知道這個問題的人,可以看的明白你在說什么。

  2. 雖然沒有碰見過這個問題,但感覺是自己熟悉的領(lǐng)域,可能知道答案,但是看了你的問題描述,也不知道你在說什么。


這個描述很長,我第一次看的時候很懵逼,很難理解他在說什么。我就是屬于第二類人。


而且在大多數(shù)的問題中,第二類人比第一類人多很多。


但是當我了解到這個 Bug 的來龍去脈的時候,再看這個描述,其實寫的很清楚了,也很好理解。我就變成第一類人了。


但是變成第一類人是有前提的,前提就是我已經(jīng)了解到了這個地方 Bug 了??上ВF(xiàn)在是提問,而被提問的人,還對這個 Bug 不是特別了解。


即使,這個被提問的人是 Doug Lea。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


可以看到,2014 年 11 月 04 日 Martin 提出這個問題后, Doug Lea 在不到一個小時內(nèi)就進行了回復,我給大家翻譯一下,老爺子回復的啥:


首先,你說你發(fā)現(xiàn)了 ConcurrentHashMap 的問題,但是我沒有看到的測試用例。那么我就猜測一下是不是有其他線程在計算值的時候被卡住了,但是從你的描述中我也看不到相應的點。 


簡單來說就是:Talk is cheap. Show me the code.(屁話少說,放碼過來。)

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


于是另一個哥們 Pardeep 在一個月后提交了一個測試案例,就是我們前面看到的測試案例:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

Pardeep 給 Martin 回復到下面這段話:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

他開門見山的說:我注意這個 bug 很長時間了,然后我還有一個測試用例。


可以說這個測試案例的出現(xiàn),才是真正的轉(zhuǎn)折點。


然后他提出了自己的看法,這段描述簡短有力的說出了問題的所在(后面我們會講到,然后他還提出了自己的意見。


不到一個小時,這個回到得到了 Doug Lea 的回復:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


他說:小伙子的建議還是不錯的,但是現(xiàn)在還不是我們解決這個問題的時候。我們也許會通過代碼改進死鎖檢查機制,以幫助用戶 debug 他們的程序。但是目前而言,這種機制就算做出來,工作效率也是非常低下的,比如在當前的這個案例下。但是現(xiàn)在我們至少清楚的知道,是否要實現(xiàn)這種機制是不能確定的。


總之一句話:問題我知道了,但是目前我還沒想到好的解決方法。


但是,在 19 天以后,老爺子又回來處理這個問題了:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


這次的回答可謂是峰回路轉(zhuǎn),他說:請忽略我之前的話。我們發(fā)現(xiàn)了一些可行的改進方法,這些改進可以處理更多的用戶錯誤,包括本報告中所提供的測試用例,即解決在 computeIfAbsent 中提供的函數(shù)中進行遞歸映射更新導致死鎖這樣的問題。我們會在 JDK 9 里面解決這個問題。


所以,回顧這個 Bug 被提出的過程。


首先是 Martin 提出了這個問題,并進行了詳細的描述??上У氖撬拿枋龊軐I(yè),是站在你已經(jīng)了解了這個 Bug 的立場上去描述的,讓人看的很懵逼。


所以 Doug Lea 看到后也表示這啥呀,沒搞懂。


然后是 Pardeep 跟進這個問題,轉(zhuǎn)折點在于他拋出的這個測試案例。而我相信,既然 Martin  能把這個問題描述的很清楚,他一定是有一個自己的測試案例的,但是他沒有展現(xiàn)出來。


所以,朋友們,測試案例的重要性不言而喻了。問問題的時候不要只是拋出異常,你至少給段對應的代碼,或者日志,或者一次性描述清楚,寫在文檔里面發(fā)出來也行呀。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


Bug 的原因

導致這個 Bug 的原因也是一句話就能說清楚,前面的 Pardeep 老哥也說了:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

問題在于我們在進行 computeIfAbsent 的時候,里面還有一個 computeIfAbsent。而這兩個 computeIfAbsent 它們的 key 對應的 hashCode 是一樣的。


你說巧不巧。


當它們的 hashCode 是一樣的時候,說明它們要往同一個槽放東西。


而當?shù)诙€元素進來的時候,發(fā)現(xiàn)坑位已經(jīng)被前一個元素占領(lǐng)了,可能就是這樣的畫風:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


接下來我們就解析一下 computeIfAbsent 方法的工作流程:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

第一步是計算 key 對應的 hashCode 應該放到哪個槽里面。


然后是進入1649 行的這個 for 循環(huán),而這個 for 循環(huán)是一個死循環(huán),它在循環(huán)體內(nèi)部判斷各種情況,如果滿足條件則 break 循環(huán)。


首先,我們看一下 “AaAa” 和 “BBBB” 經(jīng)過 spread 計算(右移 16 位高效計算)后的 h 值是什么:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


哇塞,好巧啊,從框起來的這兩部分可以看到,都是 2031775 呢。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

說明他們要在同一個槽里面搞事情。


先是 “AaAa” 進入 computeIfAbsent 方法:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

在第一次循環(huán)的時候 initTable,沒啥說的。


第二次循環(huán)先是在 1653 行計算出數(shù)組的下標,并取出該下標的 node。發(fā)現(xiàn)這個 node 是空的。于是進入分支判斷:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

在標號為 ① 的地方進行 cas 操作,先用 r(即 ReservationNode)進行一個占位的操作。


在標號為 ② 的地方進行 mappingFunction.apply 的操作,計算 value 值。如果計算出來不為 null,則把 value 組裝成最終的 node。


在標號為 ③ 的東西把之前占位的 ReservationNode 替換成標號為 ② 的地方組裝成的node 。


問題就出現(xiàn)標號為 ② 的地方??梢钥吹竭@里去進行了 mappingFunction.apply 的操作,而這個操作在我們的案例下,會觸發(fā)另一次 computeIfAbsent 操作。


現(xiàn)在 “AaAa” 就等著這個 computeIfAbsent  操作的返回值,然后進行下一步操作,也就是進行標號為 ③ 的操作了。


接著 “BBBB” 就來了。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

通過前面我們知道了 “BBBB” 的 hashCode 經(jīng)過計算后也是和 “AaAa”  一樣。所以它也要想要去那個槽里面搞事情。


可惜它來晚了一步。


帶大家看一下對應的代碼:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

當 key 為 “BBBB” 的時候,算出來的 h 值也是 2031775。


它也會進入 1649 行的這個死循環(huán)。然后進行各種判斷。


接下來我要論證的是:


在本文的示例代碼中,當運行到 key 為 “BBBB” 的時候,進入 1649 行這個死循環(huán)后,就退不出來了。程序一直在里面循環(huán)運行。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

在標號為 ① 的地方,由于這個時候 tab 已經(jīng)不為 null 了,所以不會進入這個分支。


在標號為 ② 的地方,由于之前 “AaAa” 已經(jīng)扔了一個 ReservationNode 進去占位置了,所以不等于 null。所以,也就不會進入這個分支。


怕你懵逼,給你配個圖,真是暖男作者石錘了:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


接下來到標號為 ③ 的地方,里面有一個 MOVED,這個 MOVED 是干啥的呢?

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

表示當前的 ConcurrentHashMap 是否是在進行擴容。


很明顯,現(xiàn)在還沒有到該擴容的時候:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

第 1678 行的 f 就是之前 “AaAa” 扔進去的 ReservationNode ,這個 Node 的 hash 是 -3,不等于MOVED(-1)。


所以,不會進入這個分支判斷。


接下來,能進的只有標號為 ④ 的地方了,所以我們只需要把這個地方攻破,就徹底了解這個 Bug 了。


走起:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

通過前面的分析我們知道了,當前案例情況下,只會進入 1672 行這個分支。


而這個分支里面,還有四個判斷。我們一個個的攻破:


標號為 ⑤ 的地方,tabAt 方法取出來的對象,就是之前 “AaAa” 放進去的占位的 ReservationNode ,也就是這個 f 。所以可以進入這個分支判斷。


標號為 ⑥ 的地方,fh >=0 。而 fh 是當前 node 的 hash 值,大于 0 說明當前是按照鏈表存儲的數(shù)據(jù)。之前我們分析過了,當前的 hash 值是 -3。所以,不會進入這個分支。


標號為 ⑦ 的地方,判斷 f 節(jié)點是否是紅黑樹存儲。當然不是的。所以,不會進入這個分支。


標號為 ⑧ 的地方,binCount 代表的是該下標里面,有幾個 node 節(jié)點。很明顯,現(xiàn)在一個都沒有。所以當前的 binCount 還是 0 。所以,不會進入這個分支。


完了。分析完了。


Bug 也就出來了,一次 for 循環(huán)結(jié)束后,沒有 break??嗑涂嘣谶@個 for 循環(huán)還是個死循環(huán)。


再來一個上帝視角,看看當 key 為 “BBBB” 的時候發(fā)生了什么事情:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


進入無限循環(huán)內(nèi):


①.經(jīng)過 “AaAa” 之后,tab 就不為 null 了。

②.當前的槽中已經(jīng)被 “AaAa” 先放了一個 ReservationNode 進行占位了,所以不為 null。

③.當前的 map 并沒有進行擴容操作。

④.包含⑤、⑥、⑦、⑧。

⑤.tabAt 方法取出來的對象,就是之前 “AaAa” 放進去的占位的 ReservationNode,所以滿足條件進入分支。

⑥.判斷當前是否是鏈表存儲,不滿足條件,跳過。

⑦.判斷當前是否是紅黑樹存儲,不滿足條件,跳過。

⑧.判斷當前下標里面是否放了 node,不滿足條件(“AaAa” 只有個占位的Node ,并沒有初始完成,所以還沒有放到該下標里面),進入下一次循環(huán)。


然后它就在死循環(huán)里面出不來了!

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


我相信現(xiàn)在大家對于這個 Bug 的來路了解清楚了。


如果你是在 idea 里面跑這個測試用例,也可以這樣直觀的看一眼:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


點擊這個照相機圖標:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


從線程快照里面其實也是可以看到端倪的,大家可以去分析分析。


有的觀點說的是由于線程安全的導致的死循環(huán),經(jīng)過分析我覺得這個觀點是不對的。


它存在死循環(huán),不是由于線程安全導致的,純粹是自己進入了死循環(huán)。


或者說,這是一個“彩蛋”?


或者......自信點,就說這事 Bug ,能穩(wěn)定復現(xiàn)的那種。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


那么我們?nèi)绻鞘褂?JDK 8 怎么避免踩到這個“彩蛋”呢?


看看 Dubbo 里面是怎么解決的:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


先調(diào)用了 get 方法,如果返回為 null,則調(diào)用 putIfAbsent 方法,這樣就能實現(xiàn)和之前一樣的效果了。


如果你在項目中也有使用 computeIfAbsent 的地方,建議也這樣去修改。


說到 ConcurrentHashMap get 方法返回 null,我就想起了之前討論的一個面試題了:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

答案都寫在這個文章里面了,有興趣的可以了解一下《這道面試題我真不知道面試官想要的回答是什么


Bug 的解決

其實徹底理解了這個 Bug 之后,我們再來看一下 JDK 9 里面的解決方案,看一下官方源碼對比:

http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ConcurrentHashMap.java?r1=1.258&r2=1.259&sortby=date&diff_format=f

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

就加了兩行代碼,判斷完是否是紅黑樹節(jié)點后,再判斷一下是否是 ReservationNode 節(jié)點,因為這個節(jié)點就是個占位節(jié)點。如果是,則拋出異常。


震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


就這么簡單。沒有什么神秘的。


所以,如果你在 JDK 9 里面執(zhí)行文本的測試用例,就會拋出 IllegalStateException。


這就是 Doug Lea 之前提到的解決方案:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


了解了這個 Bug 的來龍去脈后,特別是看到解決方案后,我們就能輕描淡寫的說一句:


害,就這?沒聽說過!

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

另外,我看 JDK 9 修復的時候還不止修復了一個問題:

http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/6dd59c01f011/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

你去翻一翻。發(fā)現(xiàn),啊,全是知識點啊,學不動了。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

釣魚執(zhí)法

為什么我在文章的一開始就說了這是 Doug Lea 在釣魚執(zhí)法呢?


因為在最開始提問的藝術(shù)那一部分,我相信,Doug Lea 跑完那個測試案例之后,心里也有點數(shù)了。


大概知道問題在哪了,而且從他的回答和他寫的文檔中我也有理由相信,他寫的這個方法的時候就知道可能會出問題。


而且,Pardeep 的回復中提到了文檔,那我們就去看看官方文檔對于該方法的描述是怎樣的:

https://docs.oracle.com/javase/8/docs/api/

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

文檔中說函數(shù)方法應該簡短,簡單。而且不能在更新的映射的時候更新映射。就是說不能套娃。


套娃,用程序說就是recursive(遞歸),按照文檔說如果存在遞歸,則會拋出 IllegalStateException 。


而提到遞歸,你想到了什么?


我首先就想到了斐波拉契函數(shù)。我們用 computeIfAbsent 實現(xiàn)一個斐波拉契函數(shù)如下:

public class Test {

    static Map<Integer, Integer> cache = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        System.out.println("f(" + 14 + ") =" + fibonacci(14));
    }

    static int fibonacci(int i) {
        if (i == 0)
            return i;
        if (i == 1)
            return 1;
        return cache.computeIfAbsent(i, (key) -> {
            System.out.println("Slow calculation of " + key);
            return fibonacci(i - 2) + fibonacci(i - 1);
        });
    }
}


這就是遞歸調(diào)用,我用 JDK 1.8 跑的時候并沒有拋出 IllegalStateException,只是程序假死了,原因和我們前面分析的是一樣一樣的。我理解這個地方是和文檔不符的。


所以,我懷疑是 Doug Lea 在這個地方釣魚執(zhí)法。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

CHM一定線程安全嗎?

既然都說到 currentHashMap(CHM)了,那我說一個相關(guān)的注意點吧。


首先 CHM 一定能保證線程安全嗎?


是的,CHM 本身一定是線程安全的。但是,如果你使用不當還是有可能會出現(xiàn)線程不安全的情況。


給大家看一點 Spring 中的源碼吧:

org.springframework.core.SimpleAliasRegistry

在這個類中,aliasMap 是 ConcurrentHashMap 類型的:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

在 registerAlias 和 getAliases 方法中,都有對 aliasMap 進行操作的代碼,但是在操作之前都是用 synchronized 把 aliasMap 鎖住了。


為什么?為什么我們操作 ConcurrentHashMap 的時候還要加鎖呢?

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?


這個是根據(jù)場景而定的,這個別名管理器,在這里加鎖應該是為了避免多個線程操作 ConcurrentHashMap 。


雖然 ConcurrentHashMap 是線程安全的,但是假設如果一個線程 put,一個線程 get,在這個代碼的場景里面是不允許的。


如果覺得不太好理解的話我舉一個 redis 的例子。


redis 的 get、set 方法都是線程安全的吧。但是你如果先 get 再 set,那么在多線程的情況下還是會有問題的。


因為這兩個操作不是原子性的。所以 incr 就應運而生了。


我舉這個例子的是想說線程安全與否不是絕對的,要看場景。給你一個線程安全的容器,你使用不當還是會有線程安全的問題。


再比如,HashMap 一定是線程不安全的嗎?


說不能說的這么死吧。它是一個線程不安全的容器。但是如果我的使用場景是只讀呢?


在這個只讀的場景下,它就是線程安全的。


總之,看場景。道理,就是這么一個道理。

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

長按訂閱更多精彩▼

震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?

如有收獲,點個在看,誠摯感謝

免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉