Apache commons-pool2-2.4.2源碼學習筆記

 

1.背景

       最近查看了下Apache commons-pool2的源代碼commons-pool2-2.4.2,代碼不多,大概50個java類左右,閱讀源代碼的初衷是爲了通過不斷的學習和總結,昇華自己的技術能力,寫此博客是爲了給自己留下一點筆記,日後能夠溫故知新。

       Apache commons-pool2類庫是對象池技術的一種具體實現,它的出現是爲了解決頻繁的創建和銷燬對象帶來的性能損耗問題,原理就是建立一個對象池,池中預先生成了一些對象,需要對象的時候進行租借,用完後進行歸還,對象不夠時靈活的自動創建,對象池滿後提供參數控制是否阻塞還是非阻塞響應租借.

       熟悉了Apache commons-pool2對於瞭解數據庫連接池DBCP和了解redis客戶端jedis的連接池都有很大幫助,因爲jedis的連接池就是基於Apache commons-pool2實現,而DBCP是基於Jakarta commons-pool實現。

 

2.Apache commons-pool2中3類重要接口

  •  PooledObjectFactory/KeyedPooledObjectFactory:是兩個接口,作用都是產生PooledObject的工廠,定義瞭如何makeObject創建、destroyObject銷燬、validateObject校驗、activateObject激活PooledObject對象,使用Apache commons-pool2的使用者需要自己實現這個接口
  • PooledObject:是一個接口,定義了getCreateTime獲取PooledObject創建時間,getActiveTimeMillis獲取PooledObject處於激活狀態的時間,getIdleTimeMillis獲取PooledObject空閒時間,getLastBorrowTime獲取PooledObject最近借出時間,getLastReturnTime獲取PooledObject最近歸還時間,getLastUsedTime獲取PooledObject最近使用時間。目前Apache commons-pool2提供了2個默認實現DefaultPooledObject和PooledSoftReference,一般使用DefaultPooledObject即可
  • ObjectPool/KeyedObjectPool:是兩個接口,作用都是管理池裏面的PooledObject,borrowObject借出PooledObject,returnObject歸還PooledObject,invalidateObject調用PooledObjectFactory銷燬PooledObject,addObject調用PooledObjectFactory創建PooledObject,getNumIdle給出PooledObject空閒個數,getNumActive給出PooledObject激活的個數,使用Apache commons-pool2的使用者可以使用默認的5個實現(SoftReferenceObjectPool GenericObjectPool ProxiedObjectPool GenericKeyedObjectPool ProxiedKeyedObjectPool),也可以自己實現

       其中PooledObjectFactory PooledObject ObjectPool關係和作用如下圖: 
       其中KeyedPooledObjectFactory PooledObject KeyedObjectPool關係和作用如下圖: 


3.PooledObject接口類繼承關係OOM圖

       PooledObject:是一個接口,定義了getCreateTime獲取PooledObject創建時間,getActiveTimeMillis獲取PooledObject處於激活狀態的時間,getIdleTimeMillis獲取PooledObject空閒時間,getLastBorrowTime獲取PooledObject最近借出時間,getLastReturnTime獲取PooledObject最近歸還時間,getLastUsedTime獲取PooledObject最近使用時間。目前Apache commons-pool2提供了2個默認實現DefaultPooledObject和PooledSoftReference,一般使用DefaultPooledObject即可 


  

4.對象池接口ObjectPool和KeyedObjectPool類關係

       Apache commons-pool2裏有針對對象池的5個具體實現類SoftReferenceObjectPool GenericObjectPool ProxiedObjectPool GenericKeyedObjectPool ProxiedKeyedObjectPool ,這5個對象池按照實現的接口不同分爲2類,一個是實現了接口ObjectPool,另一個是實現了接口KeyedObjectPool

    4.1 PowerDesigner反向工程ObjectPool的OOM關係圖

  • GenericObjectPool:可配置LIFO/FIFO行爲的ObjectPool的實現。默認採用LIFO隊列方式。這意味着當有閒置的可用對象在對象池中時,borrowObject方法會返回最近的實例。如果配置文件中的LIFO配置項的值爲false,則將返回相反排序的實例,也就是會返回最先進入對象池的對象的實例;
  • SoftReferenceObjectPool:使用LIFO行爲實現的ObjectPool。此外,在這個對象池實現中,每個對象都會被包裝到一個SoftReference中。SoftReference允許垃圾回收機制在需要釋放內存時回收對象池中的對象;
  • ProxiedObjectPool:使用CGLIB或者JDK自帶動態代理技術,代理由GenericObjectPool或者SoftReferenceObjectPool產生的ObjectPool對象

  

    4.2 PowerDesigner反向工程KeyedObjectPool的OOM關係圖

  • GenericKeyedObjectPool:可配置LIFO/FIFO行爲的ObjectPool的實現。默認採用LIFO隊列方式。這意味着當有閒置的可用對象在對象池中時,borrowObject方法會返回最近的實例。如果配置文件中的LIFO配置項的值爲false,則將返回相反排序的實例,也就是會返回最先進入對象池的對象的實例;
  • ProxiedKeyedObjectPool:使用CGLIB或者JDK自帶動態代理技術,代理由GenericKeyedObjectPool產生的ObjectPool對象

5. PooledObjectState類枚舉的PooledObject狀態及其轉換關係

狀態 描述
IDLE 位於隊列中,未使用
ALLOCATED 在使用
EVICTION 位於隊列中,當前正在測試,可能會被回收
EVICTION_RETURN_TO_HEAD

不在隊列中,當前正在測試,可能會被回收。

如果客戶端試圖借出該正在被測試的對象,

需等到測試完畢後才能借出並且從隊列中移除;

待測試完畢後,如果沒被回收該對象會放置在隊列的開頭位置。

VALIDATION 位於隊列中,當前正在驗證
VALIDATION_PREALLOCATED

不在隊列中,當前正在驗證。當對象從池中被借出,

在配置了testOnBorrow的情況下,對像從隊列移除和進行預分配的時候會進行驗證

VALIDATION_RETURN_TO_HEAD

不在隊列中,正在進行驗證。從池中借出對象時,

從隊列移除對象時會先進行測試。返回到隊列頭部的時候應該做一次完整的驗證

INVALID 回收或驗證失敗,將銷燬
ABANDONED 即將無效
RETURNING 返還到池中



 

6.GenericObjectPool的BorrowObject和ReturnObject流程分析

       這裏先來一張GenericObjectPool的組圖,如下: 

 

    6.1 GenericObjectPool,BorrowObject租借池化對象     6.2 GenericObjectPool.ReturnObject歸還池化對象  

7.GenericObjectPoolConfig配置屬性介紹

       GenericObjectPoolConfig是一個配置類,提供了很多參數來控制對象池的相關屬性,如果你使用過jedis,對於其JedisPoolConfig可能不陌生

	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<property name="maxTotal" value="2048" />
		<property name="maxIdle" value="200" />
		<property name="numTestsPerEvictionRun" value="1024" />
		<property name="timeBetweenEvictionRunsMillis" value="30000" />
		<property name="minEvictableIdleTimeMillis" value="-1" />
		<property name="softMinEvictableIdleTimeMillis" value="10000" />
		<property name="maxWaitMillis" value="1500" />
		<property name="testOnBorrow" value="true" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnReturn" value="false" />
		<property name="jmxEnabled" value="true" />
		<property name="blockWhenExhausted" value="false" />
	</bean>

       而JedisPoolConfig其實就是繼承自GenericObjectPoolConfig,GenericObjectPoolConfig的相關配置屬性列表如下: 

屬性 類型 默認值 作用
maxTotal int 8 池中最多可用的實例個數
maxIdle int 8 池中最多可容納的實例(instances)個數
minIdle int 0 池中最少需要容納的實例(instances)個數
lifo boolean TRUE 池中實例的操作是否按照LIFO(後進先出)的原則
fairness boolean FALSE 租借池化對象的客戶端按照FIFO進行
maxWaitMillis long -1 調用borrowObject方法時,需要等待的最長時間
minEvictableIdleTimeMillis long 1800000 池中對象處於空閒狀態開始到被回收的最短時間
softMinEvictableIdleTimeMillis long 3 池中對象處於空閒狀態開始到被回收的最短時間
numTestsPerEvictionRun int 3 池中處於空閒狀態的對象每次被檢測是否回收時
最多隻檢測3個處於空閒狀態的對象,比如該值設置爲3,此時池中有5個閒置對象,那麼每次只會檢查前三個閒置對象
evictionPolicyClassName String org.apache.commons.pool2.
impl.DefaultEvictionPolicy
回收策略
testOnCreate boolean FALSE 調用borrowObject方法時,依據此標識判斷是否
需要對返回的結果進行校驗,如果校驗失敗會刪
除當前實例,並嘗試再次獲取
testOnBorrow boolean FALSE 調用borrowObject方法時,依據此標識判斷是否
需要對返回的結果進行校驗,如果校驗失敗會
刪除當前實例,並嘗試再次獲取
testOnReturn boolean FALSE 調用returnObject方法時,依據此標識判斷是否
需要對返回的結果進行校驗
testWhileIdle boolean FALSE 閒置實例校驗標識,如果校驗失敗會刪除當前實例
timeBetweenEvictionRunsMillis long -1 閒置實例校驗器啓動的時間間隔,單位是毫秒
blockWhenExhausted boolean TRUE 當池中對象都被借出後,客戶端來租借對象,
此時是否進行阻塞還是非阻塞,默認阻塞
jmxEnabled boolean TRUE 開啓JMX開關
jmxNamePrefix String pool JMX前綴
jmxNameBase String null JMX根名字
  • 網友遇到的問題1mysql服務端設置了連接8小時失效,但是commons-pool2對應的對象池中沒有配置上timeBetweenEvictionRunsMillis minEvictableIdleTimeMillis numTestsPerEvictionRun,導致沒有對池化的mysql客戶端進行檢測,所以經驗是服務器端如果設置了idel>0的空閒時間, 那麼客戶端最好設置上對應的心跳頻率即多久心跳一次;
  • 網友遇到的問題2redis的服務端設置了timeout=0,由於網絡原因,commons-pool2已經將池中redis客戶端銷燬,但是服務端redis因爲配置了timeout=0禁用了關閉限制的redis客戶端功能,導致服務端大量殭屍進程存在,所以經驗是配置redis服務端的timeout爲一個大於0的值,意思是客戶端如果空閒了且空閒時間大於該值,服務端就會關閉該連接

 

8.Apache commons-pool2使用的基本步驟

  • 步驟一:實現自己的PooledObjectFactory
  • 步驟二:創建ObjectPool對象
  • 步驟三:從ObjectPool獲取到PooledObject對象,進行相關業務操作 

    8.1 實現自己的PooledObjectFactory

       因爲Apache commons-pool2不知道開發者需要被池化的對象,所以沒有提供默認的相關實現,開發者這一步必須做,這裏自己實現Apache commons-pool2的PooledObjectFactory管理StringBuffer爲例講解,其代碼如下: 

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
/**
 * 
 * @ClassName: MyPooledObjectFactoryExample 
 * @Description: 自己實現Apache commons-pool2的PooledObjectFactory管理StringBuffer
 * @author aperise
 * @date 2017年11月15日 下午9:22:11
 */
public class MyPooledObjectFactoryExample implements PooledObjectFactory<StringBuffer> {
	/**
	 * //創建StringBuffer對象 
	 */
	@Override
	public PooledObject<StringBuffer> makeObject() throws Exception {
		return new DefaultPooledObject<StringBuffer>(new StringBuffer());
	}
	/**
	 * //銷燬StringBuffer對象 
	 */
	@Override
	public void destroyObject(PooledObject<StringBuffer> p) throws Exception {
		StringBuffer sb = p.getObject();
		sb = null;
	}
	/**
	 * //校驗StringBuffer對象 
	 */
	@Override
	public boolean validateObject(PooledObject<StringBuffer> p) {
		return p.getObject() != null;
	}
	/**
	 * //激活StringBuffer對象 
	 */
	@Override
	public void activateObject(PooledObject<StringBuffer> p) throws Exception {
		if (null == p.getObject())
			p = new DefaultPooledObject<StringBuffer>(new StringBuffer());
	}
	/**
	 * //對話StringBuffer對象,這裏是個空實現
	 */
	@Override
	public void passivateObject(PooledObject<StringBuffer> p) throws Exception {
		// TODO Auto-generated method stub

	}
}

  

    8.2 創建ObjectPool對象

       Apache commons-pool2默認提供了對於ObjectPool的5種實現SoftReferenceObjectPool GenericObjectPool ProxiedObjectPool GenericKeyedObjectPool ProxiedKeyedObjectPool,開發者一般不需要自己實現,直接選擇使用其中一個即可,如果不用默認的實現也可以自己去實現。 

		//使用Apache commons-pool2的ObjectPool的默認實現GenericObjectPool
		ObjectPool op = new GenericObjectPool<StringBuffer>(new MyPooledObjectFactoryExample(),new GenericObjectPoolConfig());

  

    8.3 從ObjectPool獲取到PooledObject對象,進行相關業務操作 

import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

/**
 * 
 * @ClassName: CommonsPool2Test 
 * @Description: 自己實現Apache commons-pool2的PooledObjectFactory管理StringBuffer
 * @author aperise
 * @date 2017年11月15日 下午9:28:28
 */
public class CommonsPool2Test {
	public static void main(String[] args) {
		//使用Apache commons-pool2的ObjectPool的默認實現GenericObjectPool
		ObjectPool op = new GenericObjectPool<StringBuffer>(new MyPooledObjectFactoryExample(),new GenericObjectPoolConfig());
		
		//從ObjectPool租借對象StringBuffer
		StringBuffer sb = (StringBuffer) op.borrowObject();
		sb.append("aaa");
		System.out.println(sb.toString());
		//歸還對象StringBuffer
		op.returnObject(sb);
	}
}

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章