1. 程式人生 > >Apache commons-pool2-2.4.2原始碼學習筆記

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);
	}
}