前言
現(xiàn)在已經(jīng)有很多公司在使用HikariCP了,HikariCP還成為了SpringBoot默認(rèn)的連接池,伴隨著SpringBoot和微服務(wù),HikariCP 必將迎來(lái)廣泛的普及。
下面陳某帶大家從源碼角度分析一下HikariCP為什么能夠被Spring Boot 請(qǐng)來(lái),文章目錄如下:

目錄
零、類圖和流程圖
開(kāi)始前先來(lái)了解下HikariCP獲取一個(gè)連接時(shí)類間的交互流程,方便下面詳細(xì)流程的閱讀。
獲取連接時(shí)的類間交互:

圖1
一、主流程1:獲取連接流程
HikariCP獲取連接時(shí)的入口是HikariDataSource里的getConnection方法,現(xiàn)在來(lái)看下該方法的具體流程:

主流程1
上述為HikariCP獲取連接時(shí)的流程圖,由圖1可知,每個(gè)datasource對(duì)象里都會(huì)持有一個(gè)HikariPool對(duì)象,記為pool,初始化后的datasource對(duì)象pool是空的,所以第一次getConnection的時(shí)候會(huì)進(jìn)行實(shí)例化pool屬性(參考主流程1),初始化的時(shí)候需要將當(dāng)前datasource里的config屬性傳過(guò)去,用于pool的初始化,最終標(biāo)記sealed,然后根據(jù)pool對(duì)象調(diào)用getConnection方法(參考流程1.1),獲取成功后返回連接對(duì)象。
二、主流程2:初始化池對(duì)象

主流程2
該流程用于初始化整個(gè)連接池,這個(gè)流程會(huì)給連接池內(nèi)所有的屬性做初始化的工作,其中比較主要的幾個(gè)流程上圖已經(jīng)指出,簡(jiǎn)單概括一下:
利用config初始化各種連接池屬性,并且產(chǎn)生一個(gè)用于生產(chǎn)物理連接的數(shù)據(jù)源DriverDataSource
初始化存放連接對(duì)象的核心類connectionBag
初始化一個(gè)延時(shí)任務(wù)線程池類型的對(duì)象houseKeepingExecutorService,用于后續(xù)執(zhí)行一些延時(shí)/定時(shí)類任務(wù)(比如連接泄漏檢查延時(shí)任務(wù),參考流程2.2以及主流程4,除此之外maxLifeTime后主動(dòng)回收關(guān)閉連接也是交由該對(duì)象來(lái)執(zhí)行的,這個(gè)過(guò)程可以參考主流程3)
預(yù)熱連接池,HikariCP會(huì)在該流程的checkFailFast里初始化好一個(gè)連接對(duì)象放進(jìn)池子內(nèi),當(dāng)然觸發(fā)該流程得保證initializationTimeout > 0時(shí)(默認(rèn)值1),這個(gè)配置屬性表示留給預(yù)熱操作的時(shí)間(默認(rèn)值1在預(yù)熱失敗時(shí)不會(huì)發(fā)生重試)。與Druid通過(guò)initialSize控制預(yù)熱連接對(duì)象數(shù)不一樣的是,HikariCP僅預(yù)熱進(jìn)入一個(gè)連接對(duì)象。
初始化一個(gè)線程池對(duì)象addConnectionExecutor,用于后續(xù)擴(kuò)充連接對(duì)象
初始化一個(gè)線程池對(duì)象closeConnectionExecutor,用于關(guān)閉一些連接對(duì)象,怎么觸發(fā)關(guān)閉任務(wù)呢?可以參考流程1.1.2
三、流程1.1:通過(guò)HikariPool獲取連接對(duì)象

流程1.1
從最開(kāi)始的結(jié)構(gòu)圖可知,每個(gè)HikariPool里都維護(hù)一個(gè)ConcurrentBag對(duì)象,用于存放連接對(duì)象,由上圖可以看到,實(shí)際上HikariPool的getConnection就是從ConcurrentBag里獲取連接的(調(diào)用其borrow方法獲得,對(duì)應(yīng)ConnectionBag主流程),在長(zhǎng)連接檢查這塊,與之前說(shuō)的Druid不同,這里的長(zhǎng)連接判活檢查在連接對(duì)象沒(méi)有被標(biāo)記為“已丟棄”時(shí),只要距離上次使用超過(guò)500ms每次取出都會(huì)進(jìn)行檢查(500ms是默認(rèn)值,可通過(guò)配置com.zaxxer.hikari.aliveBypassWindowMs的系統(tǒng)參數(shù)來(lái)控制),emmmm,也就是說(shuō)HikariCP對(duì)長(zhǎng)連接的活性檢查很頻繁,但是其并發(fā)性能依舊優(yōu)于Druid,說(shuō)明頻繁的長(zhǎng)連接檢查并不是導(dǎo)致連接池性能高低的關(guān)鍵所在。
這個(gè)其實(shí)是由于HikariCP的無(wú)鎖實(shí)現(xiàn),在高并發(fā)時(shí)對(duì)CPU的負(fù)載沒(méi)有其他連接池那么高而產(chǎn)生的并發(fā)性能差異,后面會(huì)說(shuō)HikariCP的具體做法,即使是Druid,在獲取連接、生成連接、歸還連接時(shí)都進(jìn)行了鎖控制,因?yàn)橥ㄟ^(guò)上篇解析Druid的文章可以知道,Druid里的連接池資源是多線程共享的,不可避免地會(huì)有鎖競(jìng)爭(zhēng),有鎖競(jìng)爭(zhēng)意味著線程狀態(tài)的變化會(huì)很頻繁,線程狀態(tài)變化頻繁意味著CPU上下文切換也將會(huì)很頻繁。
回到流程1.1,如果拿到的連接為空,直接報(bào)錯(cuò),不為空則進(jìn)行相應(yīng)的檢查,如果檢查通過(guò),則包裝成ConnectionProxy對(duì)象返回給業(yè)務(wù)方,不通過(guò)則調(diào)用closeConnection方法關(guān)閉連接(對(duì)應(yīng)流程1.1.2,該流程會(huì)觸發(fā)ConcurrentBag的remove方法丟棄該連接,然后把實(shí)際的驅(qū)動(dòng)連接交給closeConnectionExecutor線程池,異步關(guān)閉驅(qū)動(dòng)連接)。
四、流程1.1.1:連接判活

流程1.1.1
承接上面的流程1.1里的判活流程,來(lái)看下判活是如何做的,首先說(shuō)驗(yàn)證方法(注意這里該方法接受的這個(gè)connection對(duì)象不是poolEntry,而是poolEntry持有的實(shí)際驅(qū)動(dòng)的連接對(duì)象),在之前介紹Druid的時(shí)候就知道,Druid是根據(jù)驅(qū)動(dòng)程序里是否存在ping方法來(lái)判斷是否啟用ping的方式判斷連接是否存活,但是到了HikariCP則更加簡(jiǎn)單粗暴,僅根據(jù)是否配置了connectionTestQuery覺(jué)定是否啟用ping:
this.isUseJdbc4Validation=?config.getConnectionTestQuery()?==?null;
所以一般驅(qū)動(dòng)如果不是特別低的版本,不建議配置該項(xiàng),否則便會(huì)走createStatement+excute的方式,相比ping簡(jiǎn)單發(fā)送心跳數(shù)據(jù),這種方式顯然更低效。
此外,這里在剛進(jìn)來(lái)還會(huì)通過(guò)驅(qū)動(dòng)的連接對(duì)象重新給它設(shè)置一遍networkTimeout的值,使之變成validationTimeout,表示一次驗(yàn)證的超時(shí)時(shí)間,為啥這里要重新設(shè)置這個(gè)屬性呢?因?yàn)樵谑褂胮ing方法校驗(yàn)時(shí),是沒(méi)辦法通過(guò)類似statement那樣可以setQueryTimeout的,所以只能由網(wǎng)絡(luò)通信的超時(shí)時(shí)間來(lái)控制,這個(gè)時(shí)間可以通過(guò)jdbc的連接參數(shù)socketTimeout來(lái)控制:
jdbc:mysql://127.0.0.1:3306/xxx?socketTimeout=250
這個(gè)值最終會(huì)被賦值給HikariCP的networkTimeout字段,這就是為什么最后那一步使用這個(gè)字段來(lái)還原驅(qū)動(dòng)連接超時(shí)屬性的原因;說(shuō)到這里,最后那里為啥要再次還原呢?這就很容易理解了,因?yàn)轵?yàn)證結(jié)束了,連接對(duì)象還存活的情況下,它的networkTimeout的值這時(shí)仍然等于validationTimeout(不合預(yù)期),顯然在拿出去用之前,需要恢復(fù)成本來(lái)的值,也就是HikariCP里的networkTimeout屬性。
五、流程1.1.2:關(guān)閉連接對(duì)象

流程1.1.2
這個(gè)流程簡(jiǎn)單來(lái)說(shuō)就是把流程1.1.1中驗(yàn)證不通過(guò)的死連接,主動(dòng)關(guān)閉的一個(gè)流程,首先會(huì)把這個(gè)連接對(duì)象從ConnectionBag里移除,然后把實(shí)際的物理連接交給一個(gè)線程池去異步執(zhí)行,這個(gè)線程池就是在主流程2里初始化池的時(shí)候初始化的線程池closeConnectionExecutor,然后異步任務(wù)內(nèi)開(kāi)始實(shí)際的關(guān)連接操作,因?yàn)橹鲃?dòng)關(guān)閉了一個(gè)連接相當(dāng)于少了一個(gè)連接,所以還會(huì)觸發(fā)一次擴(kuò)充連接池(參考主流程5)操作。
六、流程2.1:HikariCP監(jiān)控設(shè)置
不同于Druid那樣監(jiān)控指標(biāo)那么多,HikariCP會(huì)把我們非常關(guān)心的幾項(xiàng)指標(biāo)暴露給我們,比如當(dāng)前連接池內(nèi)閑置連接數(shù)、總連接數(shù)、一個(gè)連接被用了多久歸還、創(chuàng)建一個(gè)物理連接花費(fèi)多久等,HikariCP的連接池的監(jiān)控我們這一節(jié)專門(mén)詳細(xì)地分解一下,首先找到HikariCP下面的metrics文件夾,這下面放置了一些規(guī)范實(shí)現(xiàn)的監(jiān)控接口等,還有一些現(xiàn)成的實(shí)現(xiàn)(比如HikariCP自帶對(duì)prometheus、micrometer、dropwizard的支持,不太了解后面兩個(gè),prometheus下文直接稱為普羅米修斯):

圖2
下面,來(lái)著重看下接口的定義:
//這個(gè)接口的實(shí)現(xiàn)主要負(fù)責(zé)收集一些動(dòng)作的耗時(shí)publicinterfaceIMetricsTrackerextendsAutoCloseable{//這個(gè)方法觸發(fā)點(diǎn)在創(chuàng)建實(shí)際的物理連接時(shí)(主流程3),用于記錄一個(gè)實(shí)際的物理連接創(chuàng)建所耗費(fèi)的時(shí)間defaultvoidrecordConnectionCreatedMillis(longconnectionCreatedMillis){}//這個(gè)方法觸發(fā)點(diǎn)在getConnection時(shí)(主流程1),用于記錄獲取一個(gè)連接時(shí)實(shí)際的耗時(shí)defaultvoidrecordConnectionAcquiredNanos(finallongelapsedAcquiredNanos){}//這個(gè)方法觸發(fā)點(diǎn)在回收連接時(shí)(主流程6),用于記錄一個(gè)連接從被獲取到被回收時(shí)所消耗的時(shí)間defaultvoidrecordConnectionUsageMillis(finallongelapsedBorrowedMillis){}//這個(gè)方法觸發(fā)點(diǎn)也在getConnection時(shí)(主流程1),用于記錄獲取連接超時(shí)的次數(shù),每發(fā)生一次獲取連接超時(shí),就會(huì)觸發(fā)一次該方法的調(diào)用defaultvoidrecordConnectionTimeout(){}@Overridedefaultvoidclose(){}}
觸發(fā)點(diǎn)都了解清楚后,再來(lái)看看MetricsTrackerFactory的接口定義:
//用于創(chuàng)建IMetricsTracker實(shí)例,并且按需記錄PoolStats對(duì)象里的屬性(這個(gè)對(duì)象里的屬性就是類似連接池當(dāng)前閑置連接數(shù)之類的線程池狀態(tài)類指標(biāo))publicinterfaceMetricsTrackerFactory{//返回一個(gè)IMetricsTracker對(duì)象,并且把PoolStats傳了過(guò)去IMetricsTrackercreate(String?poolName,?PoolStats?poolStats);}
上面的接口用法見(jiàn)注釋,針對(duì)新出現(xiàn)的PoolStats類,我們來(lái)看看它做了什么:
publicabstractclassPoolStats{privatefinalAtomicLong?reloadAt;//觸發(fā)下次刷新的時(shí)間(時(shí)間戳)privatefinallongtimeoutMs;//刷新下面的各項(xiàng)屬性值的頻率,默認(rèn)1s,無(wú)法改變//?總連接數(shù)protectedvolatileinttotalConnections;//?閑置連接數(shù)protectedvolatileintidleConnections;//?活動(dòng)連接數(shù)protectedvolatileintactiveConnections;//?由于無(wú)法獲取到可用連接而阻塞的業(yè)務(wù)線程數(shù)protectedvolatileintpendingThreads;//?最大連接數(shù)protectedvolatileintmaxConnections;//?最小連接數(shù)protectedvolatileintminConnections;publicPoolStats(finallongtimeoutMs){this.timeoutMs?=?timeoutMs;this.reloadAt?=newAtomicLong();????}//這里以獲取最大連接數(shù)為例,其他的跟這個(gè)差不多publicintgetMaxConnections(){if(shouldLoad())?{//是否應(yīng)該刷新update();//刷新屬性值,注意這個(gè)update的實(shí)現(xiàn)在HikariPool里,因?yàn)檫@些屬性值的直接或間接來(lái)源都是HikariPool}returnmaxConnections;????}protectedabstractvoidupdate();//實(shí)現(xiàn)在↑上面已經(jīng)說(shuō)了privatebooleanshouldLoad(){//按照更新頻率來(lái)決定是否刷新屬性值for(;?;?)?{finallongnow?=?currentTime();finallongreloadTime?=?reloadAt.get();if(reloadTime?>?now)?{returnfalse;????????????}elseif(reloadAt.compareAndSet(reloadTime,?plusMillis(now,?timeoutMs)))?{returntrue;????????????}????????}????}}
實(shí)際上這里就是這些屬性獲取和觸發(fā)刷新的地方,那么這個(gè)對(duì)象是在哪里被生成并且丟給MetricsTrackerFactory的create方法的呢?這就是本節(jié)所需要講述的要點(diǎn):主流程2里的設(shè)置監(jiān)控器的流程,來(lái)看看那里發(fā)生了什么事吧:
//監(jiān)控器設(shè)置方法(此方法在HikariPool中,metricsTracker屬性就是HikariPool用來(lái)觸發(fā)IMetricsTracker里方法調(diào)用的)publicvoid?setMetricsTrackerFactory(MetricsTrackerFactory?metricsTrackerFactory)?{if(metricsTrackerFactory?!=null)?{//MetricsTrackerDelegate是包裝類,是HikariPool的一個(gè)靜態(tài)內(nèi)部類,是實(shí)際持有IMetricsTracker對(duì)象的類,也是實(shí)際觸發(fā)IMetricsTracker里方法調(diào)用的類//這里首先會(huì)觸發(fā)MetricsTrackerFactory類的create方法拿到IMetricsTracker對(duì)象,然后利用getPoolStats初始化PoolStat對(duì)象,然后也一并傳給MetricsTrackerFactorythis.metricsTracker?=?new?MetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(),?getPoolStats()));????}else{//不啟用監(jiān)控,直接等于一個(gè)沒(méi)有實(shí)現(xiàn)方法的空類this.metricsTracker?=?new?NopMetricsTrackerDelegate();????}}privatePoolStats?getPoolStats()?{//初始化PoolStats對(duì)象,并且規(guī)定1s觸發(fā)一次屬性值刷新的update方法returnnew?PoolStats(SECONDS.toMillis(1))?{@Overrideprotectedvoid?update()?{//實(shí)現(xiàn)了PoolStat的update方法,刷新各個(gè)屬性的值this.pendingThreads?=?HikariPool.this.getThreadsAwaitingConnection();this.idleConnections?=?HikariPool.this.getIdleConnections();this.totalConnections?=?HikariPool.this.getTotalConnections();this.activeConnections?=?HikariPool.this.getActiveConnections();this.maxConnections?=?config.getMaximumPoolSize();this.minConnections?=?config.getMinimumIdle();????????}????};}
到這里HikariCP的監(jiān)控器就算是注冊(cè)進(jìn)去了,所以要想實(shí)現(xiàn)自己的監(jiān)控器拿到上面的指標(biāo),要經(jīng)過(guò)如下步驟:
新建一個(gè)類實(shí)現(xiàn)IMetricsTracker接口,我們這里將該類記為IMetricsTrackerImpl
新建一個(gè)類實(shí)現(xiàn)MetricsTrackerFactory接口,我們這里將該類記為MetricsTrackerFactoryImpl,并且將上面的IMetricsTrackerImpl在其create方法內(nèi)實(shí)例化
將MetricsTrackerFactoryImpl實(shí)例化后調(diào)用HikariPool的setMetricsTrackerFactory方法注冊(cè)到Hikari連接池。
上面沒(méi)有提到PoolStats里的屬性怎么監(jiān)控,這里來(lái)說(shuō)下,由于create方法是調(diào)用一次就沒(méi)了,create方法只是接收了PoolStats對(duì)象的實(shí)例,如果不處理,那么隨著create調(diào)用的結(jié)束,這個(gè)實(shí)例針對(duì)監(jiān)控模塊來(lái)說(shuō)就失去持有了,所以這里如果想要拿到PoolStats里的屬性,就需要開(kāi)啟一個(gè)守護(hù)線程,讓其持有PoolStats對(duì)象實(shí)例,并且定時(shí)獲取其內(nèi)部屬性值,然后push給監(jiān)控系統(tǒng),如果是普羅米修斯等使用pull方式獲取監(jiān)控?cái)?shù)據(jù)的監(jiān)控系統(tǒng),可以效仿HikariCP原生普羅米修斯監(jiān)控的實(shí)現(xiàn),自定義一個(gè)Collector對(duì)象來(lái)接收PoolStats實(shí)例,這樣普羅米修斯就可以定期拉取了,比如HikariCP根據(jù)普羅米修斯監(jiān)控系統(tǒng)自己定義的MetricsTrackerFactory實(shí)現(xiàn)(對(duì)應(yīng)圖2里的PrometheusMetricsTrackerFactory類):
@OverridepublicIMetricsTrackercreate(String?poolName,?PoolStats?poolStats){????getCollector().add(poolName,?poolStats);//將接收到的PoolStats對(duì)象直接交給Collector,這樣普羅米修斯服務(wù)端每觸發(fā)一次采集接口的調(diào)用,PoolStats都會(huì)跟著執(zhí)行一遍內(nèi)部屬性獲取流程returnnewPrometheusMetricsTracker(poolName,this.collectorRegistry);//返回IMetricsTracker接口的實(shí)現(xiàn)類}//自定義的CollectorprivateHikariCPCollectorgetCollector(){if(collector?==null)?{//注冊(cè)到普羅米修斯收集中心collector?=newHikariCPCollector().register(this.collectorRegistry);????}returncollector;
通過(guò)上面的解釋可以知道在HikariCP中如何自定義一個(gè)自己的監(jiān)控器,以及相比Druid的監(jiān)控,有什么區(qū)別。 工作中很多時(shí)候都是需要自定義的,我司雖然也是用的普羅米修斯監(jiān)控,但是因?yàn)镠ikariCP原生的普羅米修斯收集器里面對(duì)監(jiān)控指標(biāo)的命名并不符合我司的規(guī)范,所以就自定義了一個(gè),有類似問(wèn)題的不妨也試一試。
這一節(jié)沒(méi)有畫(huà)圖,純代碼,因?yàn)楫?huà)圖不太好解釋這部分的東西,這部分內(nèi)容與連接池整體流程關(guān)系也不大,充其量獲取了連接池本身的一些屬性,在連接池里的觸發(fā)點(diǎn)也在上面代碼段的注釋里說(shuō)清楚了,看代碼定義可能更好理解一些。
七、流程2.2:連接泄漏的檢測(cè)與告警
本節(jié)對(duì)應(yīng)主流程2里的子流程2.2,在初始化池對(duì)象時(shí),初始化了一個(gè)叫做leakTaskFactory的屬性,本節(jié)來(lái)看下它具體是用來(lái)做什么的。
7.1:它是做什么的?
一個(gè)連接被拿出去使用時(shí)間超過(guò)leakDetectionThreshold(可配置,默認(rèn)0)未歸還的,會(huì)觸發(fā)一個(gè)連接泄漏警告,通知業(yè)務(wù)方目前存在連接泄漏的問(wèn)題。
7.2:過(guò)程詳解
該屬性是ProxyLeakTaskFactory類型對(duì)象,且它還會(huì)持有houseKeepingExecutorService這個(gè)線程池對(duì)象,用于生產(chǎn)ProxyLeakTask對(duì)象,然后利用上面的houseKeepingExecutorService延時(shí)運(yùn)行該對(duì)象里的run方法。該流程的觸發(fā)點(diǎn)在上面的流程1.1最后包裝成ProxyConnection對(duì)象的那一步,來(lái)看看具體的流程圖:

流程2.
每次在流程1.1那里生成ProxyConnection對(duì)象時(shí),都會(huì)觸發(fā)上面的流程,由流程圖可以知道,ProxyConnection對(duì)象持有PoolEntry和ProxyLeakTask的對(duì)象,其中初始化ProxyLeakTask對(duì)象時(shí)就用到了leakTaskFactory對(duì)象,通過(guò)其schedule方法可以進(jìn)行ProxyLeakTask的初始化,并將其實(shí)例傳遞給ProxyConnection進(jìn)行初始化賦值(ps:由圖知ProxyConnection在觸發(fā)回收事件時(shí),會(huì)主動(dòng)取消這個(gè)泄漏檢查任務(wù),這也是ProxyConnection需要持有ProxyLeakTask對(duì)象的原因)。
在上面的流程圖中可以知道,只有在leakDetectionThreshold不等于0的時(shí)候才會(huì)生成一個(gè)帶有實(shí)際延時(shí)任務(wù)的ProxyLeakTask對(duì)象,否則返回?zé)o實(shí)際意義的空對(duì)象。所以要想啟用連接泄漏檢查,首先要把leakDetectionThreshold配置設(shè)置上,這個(gè)屬性表示經(jīng)過(guò)該時(shí)間后借出去的連接仍未歸還,則觸發(fā)連接泄漏告警。
ProxyConnection之所以要持有ProxyLeakTask對(duì)象,是因?yàn)樗梢员O(jiān)聽(tīng)到連接是否觸發(fā)歸還操作,如果觸發(fā),則調(diào)用cancel方法取消延時(shí)任務(wù),防止誤告。
由此流程可以知道,跟Druid一樣,HikariCP也有連接對(duì)象泄漏檢查,與Druid主動(dòng)回收連接相比,HikariCP實(shí)現(xiàn)更加簡(jiǎn)單,僅僅是在觸發(fā)時(shí)打印警告日志,不會(huì)采取具體的強(qiáng)制回收的措施。
與Druid一樣,默認(rèn)也是關(guān)閉這個(gè)流程的,因?yàn)閷?shí)際開(kāi)發(fā)中一般使用第三方框架,框架本身會(huì)保證及時(shí)的close連接,防止連接對(duì)象泄漏,開(kāi)啟與否還是取決于業(yè)務(wù)是否需要,如果一定要開(kāi)啟,如何設(shè)置leakDetectionThreshold的大小也是需要考慮的一件事。
八、主流程3:生成連接對(duì)象
本節(jié)來(lái)講下主流程2里的createEntry方法,這個(gè)方法利用PoolBase里的DriverDataSource對(duì)象生成一個(gè)實(shí)際的連接對(duì)象(如果忘記DriverDatasource是哪里初始化的了,可以看下主流程2里PoolBase的initializeDataSource方法的作用),然后用PoolEntry類包裝成PoolEntry對(duì)象,現(xiàn)在來(lái)看下這個(gè)包裝類有哪些主要屬性:
finalclassPoolEntryimplementsIConcurrentBagEntry{privatestaticfinalLogger?LOGGER?=?LoggerFactory.getLogger(PoolEntry.class);//通過(guò)cas來(lái)修改state屬性privatestaticfinalAtomicIntegerFieldUpdater?stateUpdater;????Connection?connection;//實(shí)際的物理連接對(duì)象longlastAccessed;//觸發(fā)回收時(shí)刷新該時(shí)間,表示“最近一次使用時(shí)間”longlastBorrowed;//getConnection里borrow成功后刷新該時(shí)間,表示“最近一次借出的時(shí)間”@SuppressWarnings("FieldCanBeLocal")privatevolatileintstate?=0;//連接狀態(tài),枚舉值:IN_USE(使用中)、NOT_IN_USE(閑置中)、REMOVED(已移除)、RESERVED(標(biāo)記為保留中)privatevolatilebooleanevict;//是否被標(biāo)記為廢棄,很多地方用到(比如流程1.1靠這個(gè)判斷連接是否已被廢棄,再比如主流程4里時(shí)鐘回?fù)軙r(shí)觸發(fā)的直接廢棄邏輯)privatevolatileScheduledFuture?endOfLife;//用于在超過(guò)連接生命周期(maxLifeTime)時(shí)廢棄連接的延時(shí)任務(wù),這里poolEntry要持有該對(duì)象,主要是因?yàn)樵趯?duì)象主動(dòng)被關(guān)閉時(shí)(意味著不需要在超過(guò)maxLifeTime時(shí)主動(dòng)失效了),需要cancel掉該任務(wù)privatefinalFastList?openStatements;//當(dāng)前該連接對(duì)象上生成的所有的statement對(duì)象,用于在回收連接時(shí)主動(dòng)關(guān)閉這些對(duì)象,防止存在漏關(guān)的statementprivatefinalHikariPool?hikariPool;//持有pool對(duì)象privatefinalbooleanisReadOnly;//是否為只讀privatefinalbooleanisAutoCommit;//是否存在事務(wù)}
上面就是整個(gè)PoolEntry對(duì)象里所有的屬性,這里再說(shuō)下endOfLife對(duì)象,它是一個(gè)利用houseKeepingExecutorService這個(gè)線程池對(duì)象做的延時(shí)任務(wù),這個(gè)延時(shí)任務(wù)一般在創(chuàng)建好連接對(duì)象后maxLifeTime左右的時(shí)間觸發(fā),具體來(lái)看下createEntry代碼:
privatePoolEntrycreatePoolEntry(){finalPoolEntry?poolEntry?=?newPoolEntry();//生成實(shí)際的連接對(duì)象finallongmaxLifetime?=?config.getMaxLifetime();//拿到配置好的maxLifetimeif(maxLifetime?>0)?{//<=0的時(shí)候不啟用主動(dòng)過(guò)期策略//?計(jì)算需要減去的隨機(jī)數(shù)//?源注釋:variance?up?to?2.5%?of?the?maxlifetimefinallongvariance?=?maxLifetime?>10_000??ThreadLocalRandom.current().nextLong(maxLifetime?/40)?:0;finallonglifetime?=?maxLifetime?-?variance;//生成實(shí)際的延時(shí)時(shí)間poolEntry.setFutureEol(houseKeepingExecutorService.schedule(????????????????????()?->?{//實(shí)際的延時(shí)任務(wù),這里直接觸發(fā)softEvictConnection,而softEvictConnection內(nèi)則會(huì)標(biāo)記該連接對(duì)象為廢棄狀態(tài),然后嘗試修改其狀態(tài)為STATE_RESERVED,若成功,則觸發(fā)closeConnection(對(duì)應(yīng)流程1.1.2)if(softEvictConnection(poolEntry,"(connection?has?passed?maxLifetime)",false/*?not?owner?*/))?{????????????????????????????addBagItem(connectionBag.getWaitingThreadCount());//回收完畢后,連接池內(nèi)少了一個(gè)連接,就會(huì)嘗試新增一個(gè)連接對(duì)象}????????????????????},????????????????????lifetime,?MILLISECONDS));//給endOfLife賦值,并且提交延時(shí)任務(wù),lifetime后觸發(fā)}returnpoolEntry;????}//觸發(fā)新增連接任務(wù)publicvoidaddBagItem(finalintwaiting){//前排提示:addConnectionQueue和addConnectionExecutor的關(guān)系和初始化參考主流程2//當(dāng)添加連接的隊(duì)列里已提交的任務(wù)超過(guò)那些因?yàn)楂@取不到連接而發(fā)生阻塞的線程個(gè)數(shù)時(shí),就進(jìn)行提交連接新增連接的任務(wù)finalbooleanshouldAdd?=?waiting?-?addConnectionQueue.size()?>=0;//?Yes,?>=?is?intentional.if(shouldAdd)?{//提交任務(wù)給addConnectionExecutor這個(gè)線程池,PoolEntryCreator是一個(gè)實(shí)現(xiàn)了Callable接口的類,下面將通過(guò)流程圖的方式介紹該類的call方法addConnectionExecutor.submit(poolEntryCreator);????????}????}
通過(guò)上面的流程,可以知道,HikariCP一般通過(guò)createEntry方法來(lái)新增一個(gè)連接入池,每個(gè)連接被包裝成PoolEntry對(duì)象,在創(chuàng)建好對(duì)象時(shí),同時(shí)會(huì)提交一個(gè)延時(shí)任務(wù)來(lái)關(guān)閉廢棄該連接,這個(gè)時(shí)間就是我們配置的maxLifeTime,為了保證不在同一時(shí)間失效,HikariCP還會(huì)利用maxLifeTime減去一個(gè)隨機(jī)數(shù)作為最終的延時(shí)任務(wù)延遲時(shí)間,然后在觸發(fā)廢棄任務(wù)時(shí),還會(huì)觸發(fā)addBagItem,進(jìn)行連接添加任務(wù)(因?yàn)閺U棄了一個(gè)連接,需要往池子里補(bǔ)充一個(gè)),該任務(wù)則交給由主流程2里定義好的addConnectionExecutor線程池執(zhí)行,那么,現(xiàn)在來(lái)看下這個(gè)異步添加連接對(duì)象的任務(wù)流程:

addConnectionExecutor的call流
這個(gè)流程就是往連接池里加連接用的,跟createEntry結(jié)合起來(lái)說(shuō)是因?yàn)檫@倆流程是緊密相關(guān)的,除此之外,主流程5(fillPool,擴(kuò)充連接池)也會(huì)觸發(fā)該任務(wù)。
九、主流程4:連接池縮容
HikariCP會(huì)按照minIdle定時(shí)清理閑置過(guò)久的連接,這個(gè)定時(shí)任務(wù)在主流程2初始化連接池對(duì)象時(shí)被啟用,跟上面的流程一樣,也是利用houseKeepingExecutorService這個(gè)線程池對(duì)象做該定時(shí)任務(wù)的執(zhí)行器。
來(lái)看下主流程2里是怎么啟用該任務(wù)的:
//housekeepingPeriodMs的默認(rèn)值是30s,所以定時(shí)任務(wù)的間隔為30sthis.houseKeeperTask?=?houseKeepingExecutorService.scheduleWithFixedDelay(newHouseKeeper(),100L,?housekeepingPeriodMs,?MILLISECONDS);
那么本節(jié)主要來(lái)說(shuō)下HouseKeeper這個(gè)類,該類實(shí)現(xiàn)了Runnable接口,回收邏輯主要在其run方法內(nèi),來(lái)看看run方法的邏輯流程圖:

主流程4:連接池縮容
上面的流程就是HouseKeeper的run方法里具體做的事情,由于系統(tǒng)時(shí)間回?fù)軙?huì)導(dǎo)致該定時(shí)任務(wù)回收一些連接時(shí)產(chǎn)生誤差,因此存在如下判斷:
//now就是當(dāng)前系統(tǒng)時(shí)間,previous就是上次觸發(fā)該任務(wù)時(shí)的時(shí)間,housekeepingPeriodMs就是隔多久觸發(fā)該任務(wù)一次//也就是說(shuō)plusMillis(previous,?housekeepingPeriodMs)表示當(dāng)前時(shí)間//如果系統(tǒng)時(shí)間沒(méi)被回?fù)?,那么plusMillis(now,?128)一定是大于當(dāng)前時(shí)間的,如果被系統(tǒng)時(shí)間被回?fù)?/回?fù)艿臅r(shí)間超過(guò)128ms,那么下面的判斷就成立,否則永遠(yuǎn)不會(huì)成立if(plusMillis(now,128)?<?plusMillis(previous,?housekeepingPeriodMs))
這是hikariCP在解決系統(tǒng)時(shí)鐘被回?fù)軙r(shí)做出的一種措施,通過(guò)流程圖可以看到,它是直接把池子里所有的連接對(duì)象取出來(lái)挨個(gè)兒的標(biāo)記成廢棄,并且嘗試把狀態(tài)值修改為STATE_RESERVED(后面會(huì)說(shuō)明這些狀態(tài),這里先不深究)。如果系統(tǒng)時(shí)鐘沒(méi)有發(fā)生改變(絕大多數(shù)情況會(huì)命中這一塊的邏輯),由圖知,會(huì)把當(dāng)前池內(nèi)所有處于閑置狀態(tài)(STATE_NOT_IN_USE)的連接拿出來(lái),然后計(jì)算需要檢查的范圍,然后循環(huán)著修改連接的狀態(tài):
//拿到所有處于閑置狀態(tài)的連接finalList?notInUse?=?connectionBag.values(STATE_NOT_IN_USE);//計(jì)算出需要被檢查閑置時(shí)間的數(shù)量,簡(jiǎn)單來(lái)說(shuō),池內(nèi)需要保證最小minIdle個(gè)連接活著,所以需要計(jì)算出超出這個(gè)范圍的閑置對(duì)象進(jìn)行檢查inttoRemove?=?notInUse.size()?-?config.getMinIdle();for(PoolEntry?entry?:?notInUse)?{//在檢查范圍內(nèi),且閑置時(shí)間超出idleTimeout,然后嘗試將連接對(duì)象狀態(tài)由STATE_NOT_IN_USE變?yōu)镾TATE_RESERVED成功if(toRemove?>0&&?elapsedMillis(entry.lastAccessed,?now)?>?idleTimeout?&&?connectionBag.reserve(entry))?{????closeConnection(entry,"(connection?has?passed?idleTimeout)");//滿足上述條件,進(jìn)行連接關(guān)閉toRemove--;??}}fillPool();//因?yàn)榭赡芑厥樟艘恍┻B接,所以要再次觸發(fā)連接池?cái)U(kuò)充流程檢查下是否需要新增連接。
上面的代碼就是流程圖里對(duì)應(yīng)的沒(méi)有回?fù)芟到y(tǒng)時(shí)間時(shí)的流程邏輯。該流程在idleTimeout大于0(默認(rèn)等于0)并且minIdle小于maxPoolSize的時(shí)候才會(huì)啟用,默認(rèn)是不啟用的,若需要啟用,可以按照條件來(lái)配置。
十、主流程5:擴(kuò)充連接池
這個(gè)流程主要依附HikariPool里的fillPool方法,這個(gè)方法已經(jīng)在上面很多流程里出現(xiàn)過(guò)了,它的作用就是在觸發(fā)連接廢棄、連接池連接不夠用時(shí),發(fā)起擴(kuò)充連接數(shù)的操作,這是個(gè)很簡(jiǎn)單的過(guò)程,下面看下源碼(為了使代碼結(jié)構(gòu)更加清晰,對(duì)源碼做了細(xì)微改動(dòng)):
//?PoolEntryCreator關(guān)于call方法的實(shí)現(xiàn)流程在主流程3里已經(jīng)看過(guò)了,但是這里卻有倆PoolEntryCreator對(duì)象,//?這是個(gè)較細(xì)節(jié)的地方,用于打日志用,不再說(shuō)這部分,為了便于理解,只需要知道這倆對(duì)象執(zhí)行的是同一塊call方法即可privatefinalPoolEntryCreator?poolEntryCreator?=newPoolEntryCreator(null);privatefinalPoolEntryCreator?postFillPoolEntryCreator?=newPoolEntryCreator("After?adding?");privatesynchronizedvoidfillPool(){//?這個(gè)判斷就是根據(jù)當(dāng)前池子里相關(guān)數(shù)據(jù),推算出需要擴(kuò)充的連接數(shù),//?判斷方式就是利用最大連接數(shù)跟當(dāng)前連接總數(shù)的差值,與最小連接數(shù)與當(dāng)前池內(nèi)閑置的連接數(shù)的差值,取其最小的那一個(gè)得到intneedAdd?=?Math.min(maxPoolSize?-?connectionBag.size(),??minIdle?-?connectionBag.getCount(STATE_NOT_IN_USE));//減去當(dāng)前排隊(duì)的任務(wù),就是最終需要新增的連接數(shù)finalintconnectionsToAdd?=?needAdd?-?addConnectionQueue.size();for(inti?=0;?i?<?connectionsToAdd;?i++)?{//一般循環(huán)的最后一次會(huì)命中postFillPoolEntryCreator任務(wù),其實(shí)就是在最后一次會(huì)打印一次日志而已(可以忽略該干擾邏輯)addConnectionExecutor.submit((i?<?connectionsToAdd?-1)???poolEntryCreator?:?postFillPoolEntryCreator);??}}
由該過(guò)程可以知道,最終這個(gè)新增連接的任務(wù)也是交由addConnectionExecutor線程池來(lái)處理的,而任務(wù)的主題也是PoolEntryCreator,這個(gè)流程可以參考主流程3.
然后needAdd的推算:
Math.min(最大連接數(shù)-池內(nèi)當(dāng)前連接總數(shù),?最小連接數(shù)-池內(nèi)閑置的連接數(shù))
根據(jù)這個(gè)方式判斷,可以保證池內(nèi)的連接數(shù)永遠(yuǎn)不會(huì)超過(guò)maxPoolSize,也永遠(yuǎn)不會(huì)低于minIdle。在連接吃緊的時(shí)候,可以保證每次觸發(fā)都以minIdle的數(shù)量擴(kuò)容。因此如果在maxPoolSize跟minIdle配置的值一樣的話,在池內(nèi)連接吃緊的時(shí)候,就不會(huì)發(fā)生任何擴(kuò)容了。
十一、主流程6:連接回收
最開(kāi)始說(shuō)過(guò),最終真實(shí)的物理連接對(duì)象會(huì)被包裝成PoolEntry對(duì)象,存放進(jìn)ConcurrentBag,然后獲取時(shí),PoolEntry對(duì)象又會(huì)被再次包裝成ProxyConnection對(duì)象暴露給使用方的,那么觸發(fā)連接回收,實(shí)際上就是觸發(fā)ProxyConnection里的close方法:
publicfinalvoidclose()?throws?SQLException{//?原注釋:Closing?statements?can?cause?connection?eviction,?so?this?must?run?before?the?conditional?belowcloseStatements();//此連接對(duì)象在業(yè)務(wù)方使用過(guò)程中產(chǎn)生的所有statement對(duì)象,進(jìn)行統(tǒng)一close,防止漏close的情況if(delegate!=?ClosedConnection.CLOSED_CONNECTION)?{????leakTask.cancel();//取消連接泄漏檢查任務(wù),參考流程2.2try{if(isCommitStateDirty?&&?!isAutoCommit)?{//在存在執(zhí)行語(yǔ)句后并且還打開(kāi)了事務(wù),調(diào)用close時(shí)需要主動(dòng)回滾事務(wù)delegate.rollback();//回滾lastAccess?=?currentTime();//刷新"最后一次使用時(shí)間"}????}finally{delegate=?ClosedConnection.CLOSED_CONNECTION;??????poolEntry.recycle(lastAccess);//觸發(fā)回收}??}}
這個(gè)就是ProxyConnection里的close方法,可以看到它最終會(huì)調(diào)用PoolEntry的recycle方法進(jìn)行回收,除此之外,連接對(duì)象的最后一次使用時(shí)間也是在這個(gè)時(shí)候刷新的,該時(shí)間是個(gè)很重要的屬性,可以用來(lái)判斷一個(gè)連接對(duì)象的閑置時(shí)間,來(lái)看下PoolEntry的recycle方法:
voidrecycle(finallonglastAccessed){if(connection?!=null)?{this.lastAccessed?=?lastAccessed;//刷新最后使用時(shí)間hikariPool.recycle(this);//觸發(fā)HikariPool的回收方法,把自己傳過(guò)去}}
之前有說(shuō)過(guò),每個(gè)PoolEntry對(duì)象都持有HikariPool的對(duì)象,方便觸發(fā)連接池的一些操作,由上述代碼可以看到,最終還是會(huì)觸發(fā)HikariPool里的recycle方法,再來(lái)看下HikariPool的recycle方法:
voidrecycle(finalPoolEntry?poolEntry){??metricsTracker.recordConnectionUsage(poolEntry);//監(jiān)控指標(biāo)相關(guān),忽略connectionBag.requite(poolEntry);//最終觸發(fā)connectionBag的requite方法歸還連接,該流程參考ConnectionBag主流程里的requite方法部分}
以上就是連接回收部分的邏輯,相比其他流程,還是比較簡(jiǎn)潔的。
十二、ConcurrentBag主流程
這個(gè)類用來(lái)存放最終的PoolEntry類型的連接對(duì)象,提供了基本的增刪查的功能,被HikariPool持有,上面那么多的操作,幾乎都是在HikariPool中完成的,HikariPool用來(lái)管理實(shí)際的連接生產(chǎn)動(dòng)作和回收動(dòng)作,實(shí)際操作的卻是ConcurrentBag類,梳理下上面所有流程的觸發(fā)點(diǎn):
主流程2:初始化HikariPool時(shí)初始化ConcurrentBag(構(gòu)造方法),預(yù)熱時(shí)通過(guò)createEntry拿到連接對(duì)象,調(diào)用ConcurrentBag.add添加連接到ConcurrentBag。
流程1.1:通過(guò)HikariPool獲取連接時(shí),通過(guò)調(diào)用ConcurrentBag.borrow拿到一個(gè)連接對(duì)象。
主流程6:通過(guò)ConcurrentBag.requite歸還一個(gè)連接。
流程1.1.2:觸發(fā)關(guān)閉連接時(shí),會(huì)通過(guò)ConcurrentBag.remove移除連接對(duì)象,由前面的流程可知關(guān)閉連接觸發(fā)點(diǎn)為:連接超過(guò)最大生命周期maxLifeTime主動(dòng)廢棄、健康檢查不通過(guò)主動(dòng)廢棄、連接池縮容。
主流程3:通過(guò)異步添加連接時(shí),通過(guò)調(diào)用ConcurrentBag.add添加連接到ConcurrentBag,由前面的流程可知添加連接觸發(fā)點(diǎn)為:連接超過(guò)最大生命周期maxLifeTime主動(dòng)廢棄連接后、連接池?cái)U(kuò)容。
主流程4:連接池縮容任務(wù),通過(guò)調(diào)用ConcurrentBag.values篩選出需要的做操作的連接對(duì)象,然后再通過(guò)ConcurrentBag.reserve完成對(duì)連接對(duì)象狀態(tài)的修改,然后會(huì)通過(guò)流程1.1.2觸發(fā)關(guān)閉和移除連接操作。
通過(guò)觸發(fā)點(diǎn)整理,可以知道該結(jié)構(gòu)里的主要方法,就是上面觸發(fā)點(diǎn)里標(biāo)記為標(biāo)簽色的部分,然后來(lái)具體看下該類的基本定義和主要方法:
publicclassConcurrentBagimplementsAutoCloseable{privatefinalCopyOnWriteArrayList?sharedList;//最終存放PoolEntry對(duì)象的地方,它是一個(gè)CopyOnWriteArrayListprivatefinalbooleanweakThreadLocals;//默認(rèn)false,為true時(shí)可以讓一個(gè)連接對(duì)象在下方threadList里的list內(nèi)處于弱引用狀態(tài),防止內(nèi)存泄漏(參見(jiàn)備注1)privatefinalThreadLocal>?threadList;//線程級(jí)的緩存,從sharedList拿到的連接對(duì)象,會(huì)被緩存進(jìn)當(dāng)前線程內(nèi),borrow時(shí)會(huì)先從緩存中拿,從而達(dá)到池內(nèi)無(wú)鎖實(shí)現(xiàn)privatefinalIBagStateListener?listener;//內(nèi)部接口,HikariPool實(shí)現(xiàn)了該接口,主要用于ConcurrentBag主動(dòng)通知HikariPool觸發(fā)添加連接對(duì)象的異步操作(也就是主流程3里的addConnectionExecutor所觸發(fā)的流程)privatefinalAtomicInteger?waiters;//當(dāng)前因?yàn)楂@取不到連接而發(fā)生阻塞的業(yè)務(wù)線程數(shù),這個(gè)在之前的流程里也出現(xiàn)過(guò),比如主流程3里addBagItem就會(huì)根據(jù)該指標(biāo)進(jìn)行判斷是否需要新增連接privatevolatilebooleanclosed;//標(biāo)記當(dāng)前ConcurrentBag是否已被關(guān)閉privatefinalSynchronousQueue?handoffQueue;//這是個(gè)即產(chǎn)即銷的隊(duì)列,用于在連接不夠用時(shí),及時(shí)獲取到add方法里新創(chuàng)建的連接對(duì)象,詳情可以參考下面borrow和add的代碼//內(nèi)部接口,PoolEntry類實(shí)現(xiàn)了該接口publicinterfaceIConcurrentBagEntry{//連接對(duì)象的狀態(tài),前面的流程很多地方都已經(jīng)涉及到了,比如主流程4的縮容intSTATE_NOT_IN_USE?=0;//閑置intSTATE_IN_USE?=1;//使用中intSTATE_REMOVED?=?-1;//已廢棄intSTATE_RESERVED?=?-2;//標(biāo)記保留,介于閑置和廢棄之間的中間狀態(tài),主要由縮容那里觸發(fā)修改booleancompareAndSet(intexpectState,intnewState);//嘗試?yán)胏as修改連接對(duì)象的狀態(tài)值voidsetState(intnewState);//設(shè)置狀態(tài)值intgetState();//獲取狀態(tài)值}//參考上面listener屬性的解釋publicinterfaceIBagStateListener{voidaddBagItem(intwaiting);????}//獲取連接方法publicTborrow(longtimeout,finalTimeUnit?timeUnit){//?省略...}//回收連接方法publicvoidrequite(finalT?bagEntry){//省略...}//添加連接方法publicvoidadd(finalT?bagEntry){//省略...}//移除連接方法publicbooleanremove(finalT?bagEntry){//省略...}//根據(jù)連接狀態(tài)值獲取當(dāng)前池子內(nèi)所有符合條件的連接集合publicListvalues(finalintstate){//省略...}//獲取當(dāng)前池子內(nèi)所有的連接publicListvalues(){//省略...}//利用cas把傳入的連接對(duì)象的state從?STATE_NOT_IN_USE?變?yōu)?STATE_RESERVEDpublicbooleanreserve(finalT?bagEntry){//省略...}//獲取當(dāng)前池子內(nèi)符合傳入狀態(tài)值的連接數(shù)量publicintgetCount(finalintstate){//省略...}}
從這個(gè)基本結(jié)構(gòu)就可以稍微看出HikariCP是如何優(yōu)化傳統(tǒng)連接池實(shí)現(xiàn)的了,相比Druid來(lái)說(shuō),HikariCP更加偏向無(wú)鎖實(shí)現(xiàn),盡量避免鎖競(jìng)爭(zhēng)的發(fā)生。
12.1:borrow
這個(gè)方法用來(lái)獲取一個(gè)可用的連接對(duì)象,觸發(fā)點(diǎn)為流程1.1,HikariPool就是利用該方法獲取連接的,下面來(lái)看下該方法做了什么:
publicTborrow(longtimeout,finalTimeUnit?timeUnit)throwsInterruptedException{//?源注釋:Try?the?thread-local?list?firstfinalList?list?=?threadList.get();//首先從當(dāng)前線程的緩存里拿到之前被緩存進(jìn)來(lái)的連接對(duì)象集合for(inti?=?list.size()?-1;?i?>=0;?i--)?{finalObject?entry?=?list.remove(i);//先移除,回收方法那里會(huì)再次add進(jìn)來(lái)finalT?bagEntry?=?weakThreadLocals???((WeakReference)?entry).get()?:?(T)?entry;//默認(rèn)不啟用弱引用//?獲取到對(duì)象后,通過(guò)cas嘗試把其狀態(tài)從STATE_NOT_IN_USE?變?yōu)?STATE_IN_USE,注意,這里如果其他線程也在使用這個(gè)連接對(duì)象,//?并且成功修改屬性,那么當(dāng)前線程的cas會(huì)失敗,那么就會(huì)繼續(xù)循環(huán)嘗試獲取下一個(gè)連接對(duì)象if(bagEntry?!=null&&?bagEntry.compareAndSet(STATE_NOT_IN_USE,?STATE_IN_USE))?{returnbagEntry;//cas設(shè)置成功后,表示當(dāng)前線程繞過(guò)其他線程干擾,成功獲取到該連接對(duì)象,直接返回}????}//?源注釋:Otherwise,?scan?the?shared?list?...?then?poll?the?handoff?queuefinalintwaiting?=?waiters.incrementAndGet();//如果緩存內(nèi)找不到一個(gè)可用的連接對(duì)象,則認(rèn)為需要“回源”,waiters+1try{for(T?bagEntry?:?sharedList)?{//循環(huán)sharedList,嘗試把連接狀態(tài)值從STATE_NOT_IN_USE?變?yōu)?STATE_IN_USEif(bagEntry.compareAndSet(STATE_NOT_IN_USE,?STATE_IN_USE))?{//?源注釋:If?we?may?have?stolen?another?waiter's?connection,?request?another?bag?add.if(waiting?>1)?{//阻塞線程數(shù)大于1時(shí),需要觸發(fā)HikariPool的addBagItem方法來(lái)進(jìn)行添加連接入池,這個(gè)方法的實(shí)現(xiàn)參考主流程3listener.addBagItem(waiting?-1);????????????????}returnbagEntry;//cas設(shè)置成功,跟上面的邏輯一樣,表示當(dāng)前線程繞過(guò)其他線程干擾,成功獲取到該連接對(duì)象,直接返回}????????}//走到這里說(shuō)明不光線程緩存里的列表競(jìng)爭(zhēng)不到連接對(duì)象,連sharedList里也找不到可用的連接,這時(shí)則認(rèn)為需要通知HikariPool,該觸發(fā)添加連接操作了listener.addBagItem(waiting);????????timeout?=?timeUnit.toNanos(timeout);//這時(shí)候開(kāi)始利用timeout控制獲取時(shí)間do{finallongstart?=?currentTime();//嘗試從handoffQueue隊(duì)列里獲取最新被加進(jìn)來(lái)的連接對(duì)象(一般新入的連接對(duì)象除了加進(jìn)sharedList之外,還會(huì)被offer進(jìn)該隊(duì)列)finalT?bagEntry?=?handoffQueue.poll(timeout,?NANOSECONDS);//如果超出指定時(shí)間后仍然沒(méi)有獲取到可用的連接對(duì)象,或者獲取到對(duì)象后通過(guò)cas設(shè)置成功,這兩種情況都不需要重試,直接返回對(duì)象if(bagEntry?==null||?bagEntry.compareAndSet(STATE_NOT_IN_USE,?STATE_IN_USE))?{returnbagEntry;????????????}//走到這里說(shuō)明從隊(duì)列內(nèi)獲取到了連接對(duì)象,但是cas設(shè)置失敗,說(shuō)明又該對(duì)象又被其他線程率先拿去用了,若時(shí)間還夠,則再次嘗試獲取timeout?-=?elapsedNanos(start);//timeout減去消耗的時(shí)間,表示下次循環(huán)可用時(shí)間}while(timeout?>10_000);//剩余時(shí)間大于10s時(shí)才繼續(xù)進(jìn)行,一般情況下,這個(gè)循環(huán)只會(huì)走一次,因?yàn)閠imeout很少會(huì)配的比10s還大returnnull;//超時(shí),仍然返回null}finally{????????waiters.decrementAndGet();//這一步出去后,HikariPool收到borrow的結(jié)果,算是走出阻塞,所以waiters-1}}
仔細(xì)看下注釋,該過(guò)程大致分成三個(gè)主要步驟:
從線程緩存獲取連接
獲取不到再?gòu)膕haredList里獲取
都獲取不到則觸發(fā)添加連接邏輯,并嘗試從隊(duì)列里獲取新生成的連接對(duì)象
12.2:add
這個(gè)流程會(huì)添加一個(gè)連接對(duì)象進(jìn)入bag,通常由主流程3里的addBagItem方法通過(guò)addConnectionExecutor異步任務(wù)觸發(fā)添加操作,該方法主流程如下:
publicvoidadd(final?T?bagEntry){????sharedList.add(bagEntry);//直接加到sharedList里去//?源注釋:spin?until?a?thread?takes?it?or?none?are?waiting//?參考borrow流程,當(dāng)存在線程等待獲取可用連接,并且當(dāng)前新入的這個(gè)連接狀態(tài)仍然是閑置狀態(tài),且隊(duì)列里無(wú)消費(fèi)者等待獲取時(shí),發(fā)起一次線程調(diào)度while(waiters.get()?>0&&?bagEntry.getState()?==?STATE_NOT_IN_USE?&&?!handoffQueue.offer(bagEntry))?{//注意這里會(huì)offer一個(gè)連接對(duì)象入隊(duì)列yield();????}}
結(jié)合borrow來(lái)理解的話,這里在存在等待線程時(shí)會(huì)添加一個(gè)連接對(duì)象入隊(duì)列,可以讓borrow里發(fā)生等待的地方更容易poll到這個(gè)連接對(duì)象。
12.3:requite
這個(gè)流程會(huì)回收一個(gè)連接,該方法的觸發(fā)點(diǎn)在主流程6,具體代碼如下:
publicvoidrequite(final?T?bagEntry){????bagEntry.setState(STATE_NOT_IN_USE);//回收意味著使用完畢,更改state為STATE_NOT_IN_USE狀態(tài)for(inti?=0;?waiters.get()?>0;?i++)?{//如果存在等待線程的話,嘗試傳給隊(duì)列,讓borrow獲取if(bagEntry.getState()?!=?STATE_NOT_IN_USE?||?handoffQueue.offer(bagEntry))?{return;????????}elseif((i?&0xff)?==0xff)?{????????????parkNanos(MICROSECONDS.toNanos(10));????????}else{yield();????????}????}????final?List?threadLocalList?=?threadList.get();if(threadLocalList.size()?<50)?{//線程內(nèi)連接集合的緩存最多50個(gè),這里回收連接時(shí)會(huì)再次加進(jìn)當(dāng)前線程的緩存里,方便下次borrow獲取threadLocalList.add(weakThreadLocals??newWeakReference<>(bagEntry)?:?bagEntry);//默認(rèn)不啟用弱引用,若啟用的話,則緩存集合里的連接對(duì)象沒(méi)有內(nèi)存泄露的風(fēng)險(xiǎn)}}
12.4:remove
這個(gè)負(fù)責(zé)從池子里移除一個(gè)連接對(duì)象,觸發(fā)點(diǎn)在流程1.1.2,代碼如下:
publicbooleanremove(finalT?bagEntry){//?下面兩個(gè)cas操作,都是從其他狀態(tài)變?yōu)橐瞥隣顟B(tài),任意一個(gè)成功,都不會(huì)走到下面的warn?logif(!bagEntry.compareAndSet(STATE_IN_USE,?STATE_REMOVED)?&&?!bagEntry.compareAndSet(STATE_RESERVED,?STATE_REMOVED)?&&?!closed)?{????????LOGGER.warn("Attempt?to?remove?an?object?from?the?bag?that?was?not?borrowed?or?reserved:?{}",?bagEntry);returnfalse;????}//?直接從sharedList移除掉finalbooleanremoved?=?sharedList.remove(bagEntry);if(!removed?&&?!closed)?{????????LOGGER.warn("Attempt?to?remove?an?object?from?the?bag?that?does?not?exist:?{}",?bagEntry);????}returnremoved;}
這里需要注意的是,移除時(shí)僅僅移除了sharedList里的對(duì)象,各個(gè)線程內(nèi)緩存的那一份集合里對(duì)應(yīng)的對(duì)象并沒(méi)有被移除,這個(gè)時(shí)候會(huì)不會(huì)存在該連接再次從緩存里拿到呢?會(huì)的,但是不會(huì)返回出去,而是直接remove掉了,仔細(xì)看borrow的代碼發(fā)現(xiàn)狀態(tài)不是閑置狀態(tài)的時(shí)候,取出來(lái)時(shí)就會(huì)remove掉,然后也拿不出去,自然也不會(huì)觸發(fā)回收方法。
12.5:values
該方法存在重載方法,用于返回當(dāng)前池子內(nèi)連接對(duì)象的集合,觸發(fā)點(diǎn)在主流程4,代碼如下:
publicListvalues(finalint?state)?{//過(guò)濾出來(lái)符合狀態(tài)值的對(duì)象集合逆序后返回出去finalListlist=?sharedList.stream().filter(e?->?e.getState()?==?state).collect(Collectors.toList());????Collections.reverse(list);returnlist;}publicListvalues()?{//返回全部連接對(duì)象(注意下方clone為淺拷貝)return(List)?sharedList.clone();}
12.6:reserve
該方法單純將連接對(duì)象的狀態(tài)值由STATE_NOT_IN_USE修改為STATE_RESERVED,觸發(fā)點(diǎn)仍然是主流程4,縮容時(shí)使用,代碼如下:
publicbooleanreserve(finalT?bagEntry){returnbagEntry.compareAndSet(STATE_NOT_IN_USE,?STATE_RESERVED);}
12.7:getCount
該方法用于返回池內(nèi)符合某個(gè)狀態(tài)值的連接的總數(shù)量,觸發(fā)點(diǎn)為主流程5,擴(kuò)充連接池時(shí)用于獲取閑置連接總數(shù),代碼如下:
publicintgetCount(finalintstate){intcount?=0;for(IConcurrentBagEntry?e?:?sharedList)?{if(e.getState()?==?state)?{?????????count++;??????}???}returncount;}
以上就是ConcurrentBag的主要方法和處理連接對(duì)象的主要流程。
十三、總結(jié)
到這里基本上一個(gè)連接的生產(chǎn)到獲取到回收到廢棄一整個(gè)生命周期在HikariCP內(nèi)是如何管理的就說(shuō)完了,相比之前的Druid的實(shí)現(xiàn),有很大的不同,主要是HikariCP的無(wú)鎖獲取連接,本篇沒(méi)有涉及FastList的說(shuō)明,因?yàn)閺倪B接管理這個(gè)角度確實(shí)很少用到該結(jié)構(gòu),用到FastList的地方主要在存儲(chǔ)連接對(duì)象生成的statement對(duì)象以及用于存儲(chǔ)線程內(nèi)緩存起來(lái)的連接對(duì)象;
除此之外HikariCP還利用javassist技術(shù)編譯期生成了ProxyConnection的初始化,這里也沒(méi)有相關(guān)說(shuō)明,網(wǎng)上有關(guān)HikariCP的優(yōu)化有很多文章,大多數(shù)都提到了字節(jié)碼優(yōu)化、fastList、concurrentBag的實(shí)現(xiàn),本篇主要通過(guò)深入解析HikariPool和ConcurrentBag的實(shí)現(xiàn),來(lái)說(shuō)明HikariCP相比Druid具體做了哪些不一樣的操作。
轉(zhuǎn)載于:
https://mp.weixin.qq.com/s/-5ivWM6OK4RrJ1Tsn0oNjw