池化技術(shù)(JAVA)分析

簡介

池化技術(shù)能夠減少資源對象的創(chuàng)建次數(shù)问麸,提高程序的性能伙判,特別是在高并發(fā)下這種提高更加明顯。使用池化技術(shù)緩存的資源對象有如下共同特點:1衔憨,對象創(chuàng)建時間長叶圃;2,對象創(chuàng)建需要大量資源践图;3掺冠,對象創(chuàng)建后可被重復(fù)使用。下面介紹的thread码党,connection等對象都具有上面的幾個共同特點赫舒。本文通過jdk1.8的threadPool、jedis-client使用的apache-commons-pool2[2.4.2]闽瓢、以及數(shù)據(jù)庫連接池druid[1.1.10]等組件分析來感受下池化技術(shù)的使用接癌。

一個資源池具備如下功能:租用資源對象、歸還資源對象扣讼、清除過期資源對象缺猛,接下來我們就從這幾個功能點出發(fā)分別進行分析。

一 jdk1.8的ThreadPoolExecutor

線程池對外提供了一個任務(wù)提交入口execute(Runnable command)椭符,這個接口接收任務(wù)并在內(nèi)部使用線程池來執(zhí)行荔燎。我們提交多個任務(wù)的時候,線程池使用多個線程同時執(zhí)行我們的任務(wù)销钝,下面主要分析線程池線程之間是如何組織來執(zhí)行我們的任務(wù)的有咨。

ThreadPoolExecutor的核心屬性和方法

  • ThreadFactory threadFactory ---負責(zé)創(chuàng)建新的線程
  • HashSet<Worker> works ---保存當(dāng)前所有的Worker(對thread的包裝)
  • BlockingQueue<Runnable> workQueue ---(當(dāng)前的corePoolSize達到的時候,新提交的任務(wù)保存在這里)
  • int corePoolSize ---核心Worker數(shù)量
  • int maxPoolSize --- 最大的Worker數(shù)量
execute方法內(nèi)部主流程

當(dāng)前worker數(shù)量小于corePoolSize的時候創(chuàng)建新的線程并用這個線程執(zhí)行提交的任務(wù)

簡化代碼:
1 addWorker(command, true)  
2 new Worker(firstTask) ; 
3 workers.add(w); 
4 t = w.thread; 
5 t.start();

當(dāng)前worker數(shù)量大于等于corePoolSize的時候把任務(wù)添加到workQueue

workQueue.offer(command)

當(dāng)前worker數(shù)量超過了workQueue的capacity的時候創(chuàng)建新的線程并用這個線程執(zhí)行提交的任務(wù)

這里注意addWorker的第二個參數(shù)為false
1 addWorker(command, false) 
內(nèi)部使用邏輯:
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
2 new Worker(firstTask) ;
3 workers.add(w);
4 t = w.thread;
5 t.start(); 

當(dāng)前worker數(shù)量大于maxPoolSize的時候執(zhí)行拒絕策略:

reject(command);
線程重復(fù)利用以及回收

Worker核類心屬性及方法:

  • Thread thread --- 用于執(zhí)行任務(wù)的線程
  • Runnable firstTask --- 提交時候的任務(wù)
  • Worker(Runnable firstTask) --- 創(chuàng)建一個Worker
  • void run() --- 啟動thread執(zhí)行任務(wù)

Worker(Runnable firstTask)代碼片段

通過構(gòu)造方法調(diào)用threadFactory創(chuàng)建新的線程
Worker(Runnable firstTask) {
      setState(-1); // inhibit interrupts until runWorker
      this.firstTask = firstTask;
      this.thread = getThreadFactory().newThread(this);
}

run()代碼片段

直接調(diào)用ThreadPoolExecutor的runWorker(this)方法
1 while (task != null || (task = getTask()) != null) {
2 beforeExecute(wt, task);
3 task.run();
4 afterExecute(task, thrown);  線程執(zhí)行拋出的一些異常處理
}
5 processWorkerExit(w, completedAbruptly); 從works移除work

代碼1循環(huán)結(jié)束條件是沒有可執(zhí)行的任務(wù)即當(dāng)getTask()==null的時候蒸健,這時當(dāng)前線程的執(zhí)行也就結(jié)束了座享,等同于這個線程的回收;同時資源的重復(fù)利用點在于這里的循環(huán)似忧。代碼5主要是執(zhí)行了workers.remove(w)移除操作渣叛。為了保證池內(nèi)至少存在corePoolSize的work,在getTask()內(nèi)部判斷當(dāng)前workCount的數(shù)量,如果小于了coreSize盯捌,那么當(dāng)前線程會一直block淳衙,直到新的task到來。如果當(dāng)前workcount的數(shù)量超過了corePoolSize,并且workQueue為空饺著,表示這個線程不需要了箫攀,最多等待keepAliveTime的時間,也就是超過corePoolSize的線程最長存活的時間幼衰。

getTask()代碼片段如下:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ? 
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
小結(jié)

從上面的流程可以發(fā)現(xiàn)線程池沒有顯示的調(diào)用歸還線程靴跛,因為線程是一直處于待執(zhí)行狀態(tài),只要有任務(wù)到來就立即執(zhí)行塑顺;對于線程的清理汤求,就是始終判斷當(dāng)前線程數(shù)量是否滿足預(yù)設(shè)的線程池數(shù)量俏险,如果超過就不再接收新的任務(wù)自然就退出了严拒。

二 commons-pool2

commons-pool2在很多客戶端被用于資源池的實現(xiàn)扬绪,jedis-client就是其中之一。commons-pool2和上面的threadPool不同裤唠,commons-pool2內(nèi)部的資源對象有不同的狀態(tài)挤牛,使用中、空閑等狀態(tài)种蘸,并且只有空閑狀態(tài)的資源對象是可以被申請使用的墓赴。
下面主要分析commons-pool2是如何提供池的功能的。
GenericObjectPool核心類屬性及方法:

  • PooledObjectFactory<T> factory --- 用于創(chuàng)建新對象的工廠接口
  • LinkedBlockingDeque<PooledObject<T>> idleObjects --- 保存空閑資源的雙端隊列
  • T borrowObject() ---租用對象
  • returnObject(T obj) ---歸還對象

租用對象過程

1 T borrowObject() -> idleObjects.pollFirst() 
2 if(p == null) p = create() -> factory.makeObject()

歸還對象過程

void returnObject(T obj) -> idleObjects.addLast(p);
這個方法一般不是直接被調(diào)用的航瞭,而是框架(spring-data-redis)每次使用完了jedis連接后會調(diào)用jedis.close方法,這個方法進一步調(diào)用returnObject诫硕。

清除過期對象

BaseGenericObjectPool.Evictor類:
run() -> evict() -> destroy(underTest) -> idleObjects.remove(toDestory)
在設(shè)置timeBetweenEvictionRunsMillis的時候會開啟這個定時任務(wù)。
在jedis-client的使用代碼片段:
內(nèi)部維護一個GenericObjectPool對象來實現(xiàn)redis連接的復(fù)用
redis.clients.util.Pool類
public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
    if (this.internalPool != null) {
      try {
        closeInternalPool();
      } catch (Exception e) {
      }
    }
    this.internalPool = new GenericObjectPool<T>(factory, poolConfig);
  }
小結(jié)

commons-pool2的結(jié)構(gòu)比較清晰刊侯,為資源對象的存儲回收都提供了很好的api章办,在一般的項目中接入使用很容易同時也非常高效。

三 druid

druid是一個數(shù)據(jù)庫連接池滨彻,池內(nèi)維護的是數(shù)據(jù)庫的連接藕届。druid并沒有使用commons-pool2這個框架,而是自己通過數(shù)組的方式實現(xiàn)了池的所有功能亭饵。下面從druid創(chuàng)建連接休偶、租用連接以及回收連接的過程分析池的所有功能。

DruidDataSource核心類屬性及方法:

  • DruidConnectionHolder[] connections ---保存空閑的連接
  • int poolingCount ---當(dāng)前池內(nèi)資源對象的計算
  • DruidConnectionHolder[] evictConnections ---要被移除的連接
  • ReentrantLock lock --- 重入鎖保證connections數(shù)組的安全訪問
  • Condition notEmpty --- 空閑連接全部被使用等待其他客戶釋放鏈接辜羊,當(dāng)poolingCount為0的時候await,歸還連接的時候signal踏兜。
  • Condition empty --- 創(chuàng)建連接的線程里面控制,當(dāng)poolingCount為0的時候signal創(chuàng)建連接八秃,當(dāng)前active的連接超過maxActive進行await庇麦。
  • DruidPooledConnection getConnectionInternal(long maxWait) --- 獲取一個空閑連接
  • recycle(DruidPooledConnection pooledConnection) --- 回收連接

創(chuàng)建連接

DruidDataSource:
init -> CreateConnectionThread.run -> createPhysicalConnection

從數(shù)組獲取一個空閑連接

DruidDataSource:
getConnection(long maxWaitMillis) -> getConnectionInternal(maxWaitMillis) -> pollLast(nanos) -> DruidConnectionHolder last = connections[poolingCount]
這里可以看到每次都是獲取數(shù)組的最后一個元素

歸還連接

DruidPooledConnection:
DruidPooledConnection 實現(xiàn)javax.sql.PooledConnection;這個方法在框架(mybatis)里面會執(zhí)行sql操作然后在finally代碼塊執(zhí)行javax.sql.PooledConnection.close()
close() ->  recycle() -> dataSource.recycle(this) -> putLast(holder, lastActiveTimeMillis) -> connections[poolingCount] = e

關(guān)閉過期的連接

DruidDataSource:
在shrink方法內(nèi)部會判斷idleTime是否滿足條件
init -> createAndStartDestroyThread() -> run -> shrink(true, keepAlive) -> evictConnections[evictCount++] = connection -> close()
注意這里的close和上面歸還連接的close是不同的,這里是物理關(guān)閉
小結(jié)

druid沒有像commons-pool2一樣使用雙端隊列喜德,而是使用了數(shù)組;也沒有像commons-pool2一樣為對象設(shè)置多個不同的狀態(tài)山橄,druid使用兩個數(shù)組,一個用于存儲當(dāng)前可以使用的空閑連接舍悯,一個用于存儲要被清理的連接航棱。由于druid沒有使用雙端隊列,在并發(fā)不是很高的情況萌衬,數(shù)組里面最后一個連接被使用的頻率最高饮醇,極端情況只使用這一個,當(dāng)然這種情況對數(shù)據(jù)庫應(yīng)用沒有影響秕豫。

四 總結(jié)

從上面的三個組件可以看出資源對象的存儲使用了集合朴艰、雙端隊列观蓄、數(shù)組等數(shù)據(jù)結(jié)構(gòu);在面對資源競爭時使用鎖和Condition的組合來提高資源獲取的效率祠墅;當(dāng)資源不夠時侮穿,為客戶獲取資源對象提供了快速失敗、等待指定時間再失敗毁嗦,無限等待等方式亲茅。最后本文并沒有分析一些技術(shù)細節(jié),這不是本文的重點狗准。同時本文選擇的這幾個組件進行池化對比分析并不是最好克锣,比如分析數(shù)據(jù)庫連接池可以把druid、DBCP腔长、Tomcat-jdbc袭祟、C3P0來進行對比分析會更好,這是本文后續(xù)需要補充的捞附,之所以分析上面的三個組件是因為目前項目里面這幾個用的比較多巾乳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市故俐,隨后出現(xiàn)的幾起案子想鹰,更是在濱河造成了極大的恐慌,老刑警劉巖药版,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辑舷,死亡現(xiàn)場離奇詭異,居然都是意外死亡槽片,警方通過查閱死者的電腦和手機何缓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來还栓,“玉大人碌廓,你說我怎么就攤上這事∈:校” “怎么了谷婆?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辽聊。 經(jīng)常有香客問我纪挎,道長,這世上最難降的妖魔是什么跟匆? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任异袄,我火速辦了婚禮,結(jié)果婚禮上玛臂,老公的妹妹穿的比我還像新娘烤蜕。我一直安慰自己封孙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布讽营。 她就那樣靜靜地躺著虎忌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斑匪。 梳的紋絲不亂的頭發(fā)上呐籽,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天锋勺,我揣著相機與錄音蚀瘸,去河邊找鬼。 笑死庶橱,一個胖子當(dāng)著我的面吹牛贮勃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苏章,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼寂嘉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枫绅?” 一聲冷哼從身側(cè)響起泉孩,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎并淋,沒想到半個月后寓搬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡县耽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年句喷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兔毙。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡唾琼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澎剥,到底是詐尸還是另有隱情锡溯,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布哑姚,位于F島的核電站祭饭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜻懦。R本人自食惡果不足惜甜癞,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宛乃。 院中可真熱鬧悠咱,春花似錦蒸辆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眼坏,卻和暖如春拂玻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宰译。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工檐蚜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沿侈。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓闯第,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缀拭。 傳聞我的和親對象是個殘疾皇子咳短,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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