Apache Commons-pool實現對象池(包括帶key對象池)

Commons-pool是一個apache開源組織下的衆多項目的一個。其被廣泛地整合到衆多需要對象池功能的項目中

官網:http://commons.apache.org/proper/commons-pool/

本文是commons-pool的一個簡單應用,包括不帶key的池和帶key的池。帶key的池是把key相同的池對象放在一起池裏,也就是說有多少個key就有多少個池。

不帶key的池是生產完全一致的對象放在池裏,但是有時候,單用對池內所有對象一視同仁的對象池,並不能解決的問題。例如,對於一組某些參數設置不同的同類對象――比如一堆指向不同地址的 java.net.URL對象或者一批代表不同語句的java.sql.PreparedStatement對象,用這樣的方法池化,就有可能取出不合用 的對象的麻煩。這裏對帶key的池也做了簡單的例子。

Commons-pool將對象池化的工作安排給了三類對象:

1.      PoolableObjectFactory(KeyedPoolableObjectFactory):用於管理被池化對象的產生,激活,掛起,檢驗和銷燬。

2.      ObjectPool(KeyedObjectPool):用於管理要被池化的對象的接觸和歸還,並通過PoolableObjectFactory完成相應的操作。

3.      ObjectPoolFactory(KeyedObjectPoolFactory):ObjectPool的工廠,裏邊有createPool()方法,用於大量生成相同類型和設置的池。

1.下載相關jar包以及文檔

下載API以及相關jar包,本文用到的是目前最新的1.6版本。
下載源碼以及官方例子,可供參考。

2.編寫測試例子,新建項目只需要導入commons-pool-1.6.jar一個就可以

1)實體對象BaseObject.java

public class BaseObject {

	//記錄從池中取出次數
	private int num;
	private boolean active;
	
	public BaseObject(){
		active = true;
		System.out.println("new BaseObject!!!!!");
	}
//省略get set

2)管理池裏對象的產生,激活,掛起,檢驗和銷燬的工廠類

工廠類裏的方法內部可以自己根據業務邏輯去寫,例如:
makeObject()方法內部可以查詢數據庫封裝成對象,注意一次只能創建一個池對象。
validateObject()方法內部可以自己驗證該對象是否正在使用,比如可以通過對象的狀態驗證。
這是不帶key的工廠類TestPoolableFactory.java
public class TestPoolableFactory implements PoolableObjectFactory {

	//重新初始化實例返回池
	@Override
	public void activateObject(Object arg0) throws Exception {
		((BaseObject)arg0).setActive(true);
	}

	//銷燬被破壞的實例
	@Override
	public void destroyObject(Object arg0) throws Exception {
		arg0 = null;
	}

	//創建一個實例到對象池
	@Override
	public Object makeObject() throws Exception {
		BaseObject bo = new BaseObject();
		return bo;
	}

	//取消初始化實例返回到空閒對象池
	@Override
	public void passivateObject(Object arg0) throws Exception {
		((BaseObject)arg0).setActive(false);
	}

	//驗證該實例是否安全
	@Override
	public boolean validateObject(Object arg0) {
		if(((BaseObject)arg0).isActive())
			return true;
		else
			return false;
	}

}


這是帶key的工廠類TestKeyPoolableFactory.java
public class TestKeyPoolableFactory implements KeyedPoolableObjectFactory<String, BaseObject> {

	//重新初始化實例返回池
	@Override
	public void activateObject(String arg0, BaseObject arg1) throws Exception {
		((BaseObject)arg1).setActive(true);
	}

	//銷燬被破壞的實例
	@Override
	public void destroyObject(String arg0, BaseObject arg1) throws Exception {
		arg1 = null;
	}

	//創建一個實例到對象池
	@Override
	public BaseObject makeObject(String arg0) throws Exception {
		//這裏從數據庫裏查詢出使用次數最少的配置
		BaseObject bo = new BaseObject();
		bo.setNum(0);
		return bo;
	}

	//取消初始化實例返回到空閒對象池
	@Override
	public void passivateObject(String arg0, BaseObject arg1) throws Exception {
		((BaseObject)arg1).setActive(false);
	}

	//驗證該實例是否安全 true:正在使用
	@Override
	public boolean validateObject(String arg0, BaseObject arg1) {
		//這裏可以判斷實例狀態是否可用
		if(((BaseObject)arg1).isActive())
			return true;
		else
			return false;
	}
}

3)測試main方法

不帶key的main方法類PoolTest.java
public class PoolTest {

	public static void main(String[] args) {
		BaseObject bo = null;
		PoolableObjectFactory factory = new TestPoolableFactory();
		GenericObjectPool pool = new GenericObjectPool(factory);
		//這裏兩種池都可以,區別下文會提到
		//ObjectPool pool = new StackObjectPool(factory);
		try {
			for(int i = 0; i < 5; i++) {
				System.out.println("\n==========="+i+"===========");
				System.out.println("池中處於閒置狀態的實例pool.getNumIdle():"+pool.getNumIdle());
				//從池裏取一個對象,新創建makeObject或將以前閒置的對象取出來
				bo = (BaseObject)pool.borrowObject();
				System.out.println("bo:"+bo);
				System.out.println("池中所有在用實例數量pool.getNumActive():"+pool.getNumActive());
				if((i%2) == 0) {
					//用完之後歸還對象
					pool.returnObject(bo);
					System.out.println("歸還對象!!!!");
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bo != null) {
					pool.returnObject(bo);
				}
				//關閉池
				pool.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}


輸出結果:

===========0===========
池中處於閒置狀態的實例pool.getNumIdle():0
new BaseObject!!!!!
bo:common.keypool.BaseObject@1fdc96c
池中所有在用實例數量pool.getNumActive():1
歸還對象!!!!

===========1===========
池中處於閒置狀態的實例pool.getNumIdle():1
bo:common.keypool.BaseObject@1fdc96c
池中所有在用實例數量pool.getNumActive():1

===========2===========
池中處於閒置狀態的實例pool.getNumIdle():0
new BaseObject!!!!!
bo:common.keypool.BaseObject@124bbbf
池中所有在用實例數量pool.getNumActive():2
歸還對象!!!!

===========3===========
池中處於閒置狀態的實例pool.getNumIdle():1
bo:common.keypool.BaseObject@124bbbf
池中所有在用實例數量pool.getNumActive():2

===========4===========
池中處於閒置狀態的實例pool.getNumIdle():0
new BaseObject!!!!!
bo:common.keypool.BaseObject@a20892
池中所有在用實例數量pool.getNumActive():3
歸還對象!!!!

這裏的池聲明用ObjectPool或者GenericObjectPool的區別在於:

ObjectPool這種對象池的特點是:

  • 可以爲對象池指定一個初始的參考大小(當空間不夠時會自動增長)。
  • 在對象池已空的時候,調用它的borrowObject方法,會自動返回新創建的實例。
  • 可以爲對象池指定一個可保存的對象數目的上限。達到這個上限之後,再向池裏送回的對象會被自動送去回收。

GenericObjectPool這種對象池的特色是:

  • 可以設定最多能從池中借出多少個對象。
  • 可以設定池中最多能保存多少個對象。
  • 可以設定在池中已無對象可借的情況下,調用它的borrowObject方法時的行爲,是等待、創建新的實例還是拋出異常。
  • 可以分別設定對象借出和還回時,是否進行有效性檢查。
  • 可以設定是否使用一個單獨的線程,對池內對象進行後臺清理。
  • ……
這些參數可以通過一個Config類來管理,然後通過GenericObjectPool的setConfig(Config conf)方法來設置自定義的Config類。下面附上setConfig方法源碼,就知道Config類需要哪些參數了:

源碼GenericKeyedObjectPool.java類內部的setConfig方法

/**
     * Sets the configuration.
     * @param conf the new configuration to use.
     * @see GenericKeyedObjectPool.Config
     */
    public synchronized void setConfig(GenericKeyedObjectPool.Config conf) {
        setMaxIdle(conf.maxIdle);
        setMaxActive(conf.maxActive);
        setMaxTotal(conf.maxTotal);
        setMinIdle(conf.minIdle);
        setMaxWait(conf.maxWait);
        setWhenExhaustedAction(conf.whenExhaustedAction);
        setTestOnBorrow(conf.testOnBorrow);
        setTestOnReturn(conf.testOnReturn);
        setTestWhileIdle(conf.testWhileIdle);
        setNumTestsPerEvictionRun(conf.numTestsPerEvictionRun);
        setMinEvictableIdleTimeMillis(conf.minEvictableIdleTimeMillis);
        setTimeBetweenEvictionRunsMillis(conf.timeBetweenEvictionRunsMillis);
    }

源碼GenericObjectPool.java類內部的setConfig方法

/**
     * Sets my configuration.
     *
     * @param conf configuration to use.
     * @see GenericObjectPool.Config
     */
    public void setConfig(GenericObjectPool.Config conf) {
        synchronized (this) {
            setMaxIdle(conf.maxIdle);
            setMinIdle(conf.minIdle);
            setMaxActive(conf.maxActive);
            setMaxWait(conf.maxWait);
            setWhenExhaustedAction(conf.whenExhaustedAction);
            setTestOnBorrow(conf.testOnBorrow);
            setTestOnReturn(conf.testOnReturn);
            setTestWhileIdle(conf.testWhileIdle);
            setNumTestsPerEvictionRun(conf.numTestsPerEvictionRun);
            setMinEvictableIdleTimeMillis(conf.minEvictableIdleTimeMillis);
            setTimeBetweenEvictionRunsMillis(conf.timeBetweenEvictionRunsMillis);
            setSoftMinEvictableIdleTimeMillis(conf.softMinEvictableIdleTimeMillis);
            setLifo(conf.lifo);
        }
        allocate();
    }

1.      參數maxActive指明能從池中借出的對象的最大數目。如果這個值不是正數,表示沒有限制。

2.      參數whenExhaustedA ction指定在池中借出對象的數目已達極限的情況下,調用它的borrowObject方法時的行爲。可以選用的值有:

  • GenericObjectPool.WHEN_EXHAUSTED_BLOCK,表示等待;
  • GenericObjectPool.WHEN_EXHAUSTED_GROW,表示創建新的實例(不過這就使maxActive參數失去了意義);
  • GenericObjectPool.WHEN_EXHAUSTED_FAIL,表示拋出一個java.util.NoSuchElementException異常。

3.      參數maxWait指明若在對象池空時調用borrowObject方法的行爲被設定成等待,最多等待多少毫秒。如果等待時間超過了這個數值,則會拋出一個java.util.NoSuchElementException異常。如果這個值不是正數,表示無限期等待。

4.      參數testOnBorrow設定在借出對象時是否進行有效性檢查。

5.      參數testOnBorrow設定在還回對象時是否進行有效性檢查。

6.      參數timeBetweenEvictionRunsMillis,設定間隔每過多少毫秒進行一次後臺對象清理的行動。如果這個值不是正數,則實際上不會進行後臺對象清理。

7.      參數minEvictableIdleTimeMillis,設定在進行後臺對象清理時,視休眠時間超過了多少毫秒的對象爲過期。過期的對象將被回收。如果這個值不是正數,那麼對休眠時間沒有特別的約束。

8.      參數testWhileIdle,則設定在進行後臺對象清理時,是否還對沒有過期的池內對象進行有效性檢查。不能通過有效性檢查的對象也將被回收。

9.      參數lifo,池對象的放入和取出默認是後進先出的原則,默認是true,代表後進後出,設置爲false代表先進先出。


帶key的main方法類KeyPoolTest.java

public class KeyPoolTest {

	public static void main(String[] args) {
		
		BaseObject bo = null;
		BaseObject bo1 = null;
		BaseObject bo2 = null;
		
		KeyedPoolableObjectFactory<String, BaseObject> keyFactory = new TestKeyPoolableFactory();
		GenericKeyedObjectPool<String, BaseObject> keyPool = new GenericKeyedObjectPool<String, BaseObject>(keyFactory);
		//keyPool.setLifo(false);
		try {
			//這裏添加池對象,只需要傳入key就會默認調用makeObject()方法創建一個對象
			keyPool.addObject("一級");
			keyPool.addObject("二級");
			//這裏註釋掉,不初始創建這個鍵的池對象
			//keyPool.addObject("三級");
			System.out.println("池中處於閒置狀態的實例pool.getNumIdle():"+keyPool.getNumIdle());
			for (int i = 0; i < 5; i++) {
				//從池裏取對象
				bo = keyPool.borrowObject("一級");
				bo.setNum(bo.getNum()+1);
				System.out.println("一級"+i+"-------"+bo+"-------"+bo.getNum());
				
				bo1 = keyPool.borrowObject("二級");
				bo1.setNum(bo1.getNum()+1);
				System.out.println("二級"+i+"-------"+bo1+"-------"+bo1.getNum());
				//上邊註釋掉的那行代碼,這裏取對象的時候如果沒有閒置對象,也會默認去創建一個key="三級"的池對象
				bo2 = keyPool.borrowObject("三級");
				bo2.setNum(bo2.getNum()+1);
				System.out.println("三級"+i+"-------"+bo2+"-------"+bo2.getNum());
				
				if(i<3) {
					//用完之後歸還對象
					keyPool.returnObject("一級", bo);
					keyPool.returnObject("二級", bo1);
					keyPool.returnObject("三級", bo2);
					System.out.println("歸還對象!!!");
				}
			}
			//當前池裏的實例數量
			System.out.println("池中所有在用實例pool.getNumActive():"+keyPool.getNumActive());
			//當前池裏的處於閒置狀態的實例
			System.out.println("池中處於閒置狀態的實例pool.getNumIdle():"+keyPool.getNumIdle());
		} catch (Exception e) {
			e.printStackTrace();
		}
		//這裏就不寫finally了,偷懶了,這裏應該關閉池的
	}
}

輸出結果:

new BaseObject!!!!!
new BaseObject!!!!!
池中處於閒置狀態的實例pool.getNumIdle():2
一級0-------common.keypool.BaseObject@158b649-------1
二級0-------common.keypool.BaseObject@127734f-------1
new BaseObject!!!!!
三級0-------common.keypool.BaseObject@1037c71-------1
歸還對象!!!
一級1-------common.keypool.BaseObject@158b649-------2
二級1-------common.keypool.BaseObject@127734f-------2
三級1-------common.keypool.BaseObject@1037c71-------2
歸還對象!!!
一級2-------common.keypool.BaseObject@158b649-------3
二級2-------common.keypool.BaseObject@127734f-------3
三級2-------common.keypool.BaseObject@1037c71-------3
歸還對象!!!
一級3-------common.keypool.BaseObject@158b649-------4
二級3-------common.keypool.BaseObject@127734f-------4
三級3-------common.keypool.BaseObject@1037c71-------4
new BaseObject!!!!!
一級4-------common.keypool.BaseObject@1df073d-------1
new BaseObject!!!!!
二級4-------common.keypool.BaseObject@1546e25-------1
new BaseObject!!!!!
三級4-------common.keypool.BaseObject@b66cc-------1
池中所有在用實例pool.getNumActive():6
池中處於閒置狀態的實例pool.getNumIdle():0

通過輸出結果可以看出:

1.對象取出之後可以對對象進行更改,再放回池裏這個更改是保留的。

2.池對象的放入和取出默認是後進先出的原則,可以通過池pool的setLifo(boolean lifo)方法設置,默認是true,代表後進先出,設置爲false代表先進先出。如果爲了池對象使用均衡,推薦使用false。

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