溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

HikariCP中的ConcurrentBag怎么用

發(fā)布時間:2022-01-07 16:55:42 來源:億速云 閱讀:114 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“HikariCP中的ConcurrentBag怎么用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“HikariCP中的ConcurrentBag怎么用”吧!

我們知道 SpringBoot 默認連接池就是 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)化思路?!?/p>

ConcurrentBag

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

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

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

  • ThreadLocal 緩存,加快本地連接獲取速度
  • CopyOnWriteArrayList,寫時拷貝List
  • SynchronousQueue,無存儲的等待隊列
HikariCP中的ConcurrentBag怎么用  

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

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

其實這種思想很簡單。

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

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

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

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

HikariCP中的ConcurrentBag怎么用  

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

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

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

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

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

HikariCP中的ConcurrentBag怎么用  

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

HikariCP中的ConcurrentBag怎么用  

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

當(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 的哈希表擴容的負載因子就會變大,來避免 fork 期間不必要的內(nèi)存寫入操作 (擴容)。

感謝各位的閱讀,以上就是“HikariCP中的ConcurrentBag怎么用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對HikariCP中的ConcurrentBag怎么用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI