ConcurrentBag 聽過沒男窟?好家伙高并發(fā)知識點十分密集盆赤!

你好,我是 yes歉眷。

今天給大家剖析下一個叫 ConcurrentBag 的并發(fā)集合類牺六,對 C# 熟悉的同學(xué)應(yīng)該聽過這個名字,不過我今天介紹的是 HikariCP 中的 ConcurrentBag汗捡。

我們知道 SpringBoot 默認(rèn)連接池就是 HikariCP淑际,而 HikariCP 就是以快著稱的,而這個快離不開 ConcurrentBag。

如果你看過很多源碼你就會發(fā)現(xiàn)好多框架都會自定義集合類庸追,因為 JDK 通用的集合需要照顧到很多場景霍骄,而定制化肯定優(yōu)于普適化

像 HikariCP 就沒有用 ArrayList 而是定義了一個 FastList淡溯,因為 ArrayList 每次 get 都會有范圍檢查读整,并且 remove 是從前往后遍歷的。

而在 HikariCP 這個場景每次 get 范圍檢查沒有必要咱娶,并且 remove 的時候從后往前遍歷更好米间,所以就定制化了。

HikariCP 還有很多優(yōu)化膘侮,這篇文章我們就談?wù)勂渲兄磺簿褪墙裉斓闹鹘蔷褪?ConcurrentBag 。

不過今天的目的不是為了分析 HikariCP 琼了,而只是介紹這個集合類逻锐。

從它身上找點優(yōu)化的思路,到時候像面試官問你如何設(shè)計一個連接池的時候就可以搬出來:“哎呀雕薪,我有個優(yōu)化思路昧诱。”

ConcurrentBag

一般而言我們設(shè)計一個連接池的初始想法是用鎖來保證線程安全所袁,或者用一些線程安全的并發(fā)容器來存儲連接盏档。

而 HikariCP 不滿足于此,它專門設(shè)計了 ConcurrentBag 用來存數(shù)據(jù)庫連接燥爷,當(dāng) HikariPool#getConnection 的時候就是去 ConcurrentBag 拿連接蜈亩。

ConcurrentBag 整體就是無鎖設(shè)計,有三個重要的成員變量:

  • ThreadLocal 緩存前翎,加快本地連接獲取速度
  • CopyOnWriteArrayList稚配,寫時拷貝List
  • SynchronousQueue,無存儲的等待隊列

獲取數(shù)據(jù)庫連接基本流程如下:

  1. 當(dāng)取連接的時候會先去 ThreadLocal 去找以前用過的連接鱼填,如果找到連接狀態(tài)是可以使用的話拿直接返回药有。
    (ThreadLocal 是本地資源,每個線程都優(yōu)先去自己本地去找苹丸,所以競爭也更少愤惰,需要遍歷的連接也更少,所以速度就更快)
  2. 找不到再去 sharedList 這個共享的寫時復(fù)制列表中查找可用連接赘理。
  3. 如果再找不到宦言,則通過 handoffQueue 等待可用的連接,如果超過一定時間則返回 null商模。

其實這種思想很簡單奠旺。

每個線程一開始本地資源肯定是空的蜘澜,然后每個線程把自己用過的連接存起來,之后優(yōu)先用存著的鏈接响疚。

久而久之每個線程都會有自己的本地存儲的連接鄙信,這樣大家都用自己的就少了競爭,那速度不就快了忿晕?

我們再來看下取連接的源碼装诡,里面還是有一些細(xì)節(jié)的。

其實應(yīng)該叫借連接践盼,因為要還的鸦采,而且也不是把連接從 ConcurrentBag 移除,只是返回一個引用罷了咕幻。

細(xì)節(jié)已經(jīng)在代碼上標(biāo)注了渔伯,這里強調(diào)一下借連接不是移除連接,別的線程還是能通過 sharedList 找到這個連接的肄程,無非這個連接如果被占用則狀態(tài)是 STATE_IN_USE锣吼,這樣別的線程就不會用這個連接了。

總體思路就是從本地找蓝厌,沒有的話再去每個線程都能訪問的 sharedList 找吐限,再沒有就等著。

這里還有個竊取的概念褂始,其實沒什么花頭,就是充分利用連接描函。

無非就是本來屬于某個線程的本地連接崎苗,當(dāng)它歸還連接的時,恰巧有另一個線程從 sharedList 遍歷找到這個連接舀寓,這時候連接的狀態(tài)是 STATE_NOT_IN_USE胆数,那么這個連接就會被另一個線程也保存到 ThreadLocal 中了。

這就是竊取互墓,我們再來看下歸還連接的代碼必尼,連接就是在這里保存到 ThreadLocal 中的。

我在《HikariCP數(shù)據(jù)庫連接池實戰(zhàn)》這本書中看到篡撵,歸還連接的代碼在 HikariCP 2.6.0 是長下面這個樣子的

先停下來想想看有沒有啥問題判莉?

當(dāng)前歸還連接的線程需要等這個連接被其他線程取走時或者沒有等待線程時才能擺脫這個循環(huán)。

但是會出現(xiàn)一種情況:在設(shè)置連接為可用時育谬,這個連接已經(jīng)被其他線程借走了券盅,然后當(dāng)前線程還傻傻的執(zhí)行循環(huán),而恰巧等待線程一直有膛檀,但是每次 handoffQueue.offer 就是沒線程取锰镀,然后 yield 娘侍,如此往復(fù)。

這就造成明明連接已經(jīng)歸還了泳炉,而歸還的線程還做無用功的自旋操作憾筏,所以就做優(yōu)化成上面的代碼,如果bagEntry.getState() != STATE_NOT_IN_USE 說明已經(jīng)被別的線程借去用了花鹅,所以直接 return氧腰。

再提一提 CopyOnWriteArrayList 吧。

連接池是一個典型的讀多寫少的場景翠胰,所以寫時復(fù)制用在此處再合適不過了容贝。

簡單的說:寫操作的時候會復(fù)制當(dāng)前的 list 來做修改,等修改完了再替換老的 list之景。

在替換之前讀的線程讀取的是老的 list 的數(shù)據(jù)斤富,這樣就能做到讀的時候是無鎖的。

寫時復(fù)制的缺點就是內(nèi)存的占用锻狗,因為需要拷貝一份數(shù)據(jù)满力,如果數(shù)據(jù)很大的話那就需要考慮內(nèi)容的占用量了。

比如操作系統(tǒng)進程的 fork 操作也會用到寫時復(fù)制轻纪,子進程和父進程一開始共享數(shù)據(jù)油额,當(dāng)有修改的時候就會拷貝一份。

在 Redis 的 BGSAVE 命令或者 BGREWRITEAOF 命令的過程中就會 fork 子進程來進行后臺操作刻帚,而此時 Redis 的哈希表擴容的負(fù)載因子就會變大潦嘶,來避免 fork 期間不必要的內(nèi)存寫入操作 (擴容)。

最后

所以 ConcurrentBag 的優(yōu)化思路就是本地緩存有的去本地緩存找連接崇众,找不到就去公共的 sharedList 去找掂僵,還找不到就等著。

通過將連接本地存儲化來減少競爭顷歌,又根據(jù)連接池讀多寫少的特性用 CopyOnWriteArrayList 來實現(xiàn) sharedList 锰蓬。

當(dāng)然還有像上面 borrow 和 requite 的一些細(xì)節(jié)也值得品味,追求極致速度就需要扣細(xì)節(jié)眯漩。

更多文章可看我的文章匯總:https://github.com/yessimida/yes 歡迎 star !


我是 yes芹扭,從一點點到億點點,歡迎在看赦抖、轉(zhuǎn)發(fā)舱卡、留言,我們下篇見队萤。

巨人的肩膀

《HikariCP數(shù)據(jù)庫連接池實戰(zhàn)》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灼狰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浮禾,更是在濱河造成了極大的恐慌交胚,老刑警劉巖份汗,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蝴簇,居然都是意外死亡杯活,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門熬词,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旁钧,“玉大人,你說我怎么就攤上這事互拾⊥峤瘢” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵颜矿,是天一觀的道長寄猩。 經(jīng)常有香客問我,道長骑疆,這世上最難降的妖魔是什么田篇? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮箍铭,結(jié)果婚禮上泊柬,老公的妹妹穿的比我還像新娘。我一直安慰自己诈火,他們只是感情好兽赁,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冷守,像睡著了一般闸氮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上教沾,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音译断,去河邊找鬼授翻。 笑死,一個胖子當(dāng)著我的面吹牛孙咪,可吹牛的內(nèi)容都是我干的堪唐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼翎蹈,長吁一口氣:“原來是場噩夢啊……” “哼淮菠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荤堪,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤合陵,失蹤者是張志新(化名)和其女友劉穎枢赔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拥知,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡踏拜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了低剔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片速梗。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖襟齿,靈堂內(nèi)的尸體忽然破棺而出姻锁,到底是詐尸還是另有隱情,我是刑警寧澤猜欺,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布位隶,位于F島的核電站,受9級特大地震影響替梨,放射性物質(zhì)發(fā)生泄漏钓试。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一副瀑、第九天 我趴在偏房一處隱蔽的房頂上張望弓熏。 院中可真熱鬧,春花似錦糠睡、人聲如沸挽鞠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽信认。三九已至,卻和暖如春均抽,著一層夾襖步出監(jiān)牢的瞬間嫁赏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工油挥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留潦蝇,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓深寥,卻偏偏與公主長得像攘乒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惋鹅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容