apache-common pool的使用
Apache commons-pool本質上是"對象池",即通過一定的規則來維護對象集合的容器;commos-pool在很多場景中,用來實現"連接池"/"任務worker池"等,大家常用的dbcp數據庫連接池,也是基于commons-pool實現.
commons-pool實現思想非常簡單,它主要的作用就是將"對象集合"池化,任何通過pool進行對象存取的操作,都會嚴格按照"pool配置"(比如池的大小)實時的創建對象/阻塞控制/銷毀對象等.它在一定程度上,實現了對象集合的管理以及對象的分發.
1) 將創建對象的方式,使用工廠模式;
2) 通過"pool配置"來約束對象存取的時機
3) 將對象列表保存在隊列中(LinkedList)
首選需要聲明,不同的"對象池"(或者連接池)在設計上可能存在很大的區別,但是在思想上大同小異,本文主要講解commons-pool,它和其他"連接池"的區別在此不多討論.
一.對象生命周期
二.Config詳解:
- maxActive: 鏈接池中最大連接數,默認為8.
- maxIdle: 鏈接池中最大空閑的連接數,默認為8.
- minIdle: 連接池中最少空閑的連接數,默認為0.
- maxWait: 當連接池資源耗盡時,調用者最大阻塞的時間,超時將跑出異常。單位,毫秒數;默認為-1.表示永不超時.
- minEvictableIdleTimeMillis: 連接空閑的最小時間,達到此值后空閑連接將可能會被移除。負值(-1)表示不移除。
- softMinEvictableIdleTimeMillis: 連接空閑的最小時間,達到此值后空閑鏈接將會被移除,且保留“minIdle”個空閑連接數。默認為-1.
- numTestsPerEvictionRun: 對于“空閑鏈接”檢測線程而言,每次檢測的鏈接資源的個數。默認為3.
- testOnBorrow: 向調用者輸出“鏈接”資源時,是否檢測是有有效,如果無效則從連接池中移除,并嘗試獲取繼續獲取。默認為false。建議保持默認值.
- testOnReturn: 向連接池“歸還”鏈接時,是否檢測“鏈接”對象的有效性。默認為false。建議保持默認值.
- testWhileIdle: 向調用者輸出“鏈接”對象時,是否檢測它的空閑超時;默認為false。如果“鏈接”空閑超時,將會被移除。建議保持默認值.
- timeBetweenEvictionRunsMillis: “空閑鏈接”檢測線程,檢測的周期,毫秒數。如果為負值,表示不運行“檢測線程”。默認為-1.
- whenExhaustedAction: 當“連接池”中active數量達到閥值時,即“鏈接”資源耗盡時,連接池需要采取的手段, 默認為1:
-> 0 : 拋出異常,
-> 1 : 阻塞,直到有可用鏈接資源
-> 2 : 強制創建新的鏈接資源
這些屬性均可以在org.apache.commons.pool.impl.GenericObjectPool.Config中進行設定。
三.原理解析
1) 對象池創建(參考GenericObjectPool):
- public GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) : 此方法創建一個GenericObjectPool實例,GenericObjectPool類已經實現了和對象池有關的所有核心操作,開發者可以通過繼承或者封裝的方式來使用它.通過此構造函數,我們能夠清晰的看到,一個Pool中需要指定PoolableObjectFactory 實例,以及此對象池的Config信息.PoolableObjectFactory主要用來"創建新對象",比如當對象池中的對象不足時,可以使用 PoolableObjectFactory.makeObject()方法來創建對象,并交付給Pool管理.
此構造函數實例化了一個LinkedList作為"對象池"容器,用來存取"對象".此外還會根據timeBetweenEvictionRunsMillis的值來決定是否啟動一個后臺線程,此線程用來周期性掃描pool中的對象列表,已檢測"對象池中的對象"空閑(idle)的時間是否達到了閥值,如果是,則移除此對象.
- if ((getMinEvictableIdleTimeMillis() > 0) &&
- (idleTimeMilis > getMinEvictableIdleTimeMillis())) {
- removeObject = true;
- }
- ...
- if (removeObject) {
- try {
- _factory.destroyObject(pair.value);
- } catch(Exception e) {
- // ignored
- }
- }
2) 對象工廠PoolableObjectFactory接口:
commons-pool通過使用ObjectFactory(工廠模式)的方式將"對象池中的對象"的創建/檢測/銷毀等特性解耦出來,這是一個非常良好的設計思想.此接口有一個抽象類BasePoolableObjectFactory,可供開發者繼承和實現.
- Object makeObject() : 創建一個新對象;當對象池中的對象個數不足時,將會使用此方法來"輸出"一個新的"對象",并交付給對象池管理.
- void destroyObject(Object obj) : 銷毀對象,如果對象池中檢測到某個"對象"idle的時間超時,或者操作者向對象池"歸還對象"時檢測到"對象"已經無效,那么此時將會導致"對象銷毀";"銷毀對象"的操作設計相差甚遠,但是必須明確:當調用此方法時,"對象"的生命周期必須結束.如果object是線程,那么此時線程必須退出;如果object是socket操作,那么此時socket必須關閉;如果object是文件流操作,那么此時"數據flush"且正常關閉.
- boolean validateObject(Object obj) : 檢測對象是否"有效";Pool中不能保存無效的"對象",因此"后臺檢測線程"會周期性的檢測Pool中"對象"的有效性,如果對象無效則會導致此對象從Pool中移除,并destroy;此外在調用者從Pool獲取一個"對象"時,也會檢測"對象"的有效性,確保不能講"無效"的對象輸出給調用者;當調用者使用完畢將"對象歸還"到Pool時,仍然會檢測對象的有效性.所謂有效性,就是此"對象"的狀態是否符合預期,是否可以對調用者直接使用;如果對象是Socket,那么它的有效性就是socket的通道是否暢通/阻塞是否超時等.
- void activateObject(Object obj) : "激活"對象,當Pool中決定移除一個對象交付給調用者時額外的"激活"操作,比如可以在activateObject方法中"重置"參數列表讓調用者使用時感覺像一個"新創建"的對象一樣;如果object是一個線程,可以在"激活"操作中重置"線程中斷標記",或者讓線程從阻塞中喚醒等;如果 object是一個socket,那么可以在"激活操作"中刷新通道,或者對socket進行鏈接重建(假如socket意外關閉)等.
- void void passivateObject(Object obj) : "鈍化"對象,當調用者"歸還對象"時,Pool將會"鈍化對象";鈍化的言外之意,就是此"對象"暫且需要"休息"一下.如果object是一個 socket,那么可以passivateObject中清除buffer,將socket阻塞;如果object是一個線程,可以在"鈍化"操作中將線程sleep或者將線程中的某個對象wait.需要注意的時,activateObject和passivateObject兩個方法需要對應,避免死鎖或者"對象"狀態的混亂.
3) ObjectPool接口與實現:
對象池是commons-pool的核心接口,用來維護"對象列表"的存取;其中GenericObjectPool是其實現類,它已經實現了相關的功能.
- Object borrowObject() : 從Pool獲取一個對象,此操作將導致一個"對象"從Pool移除(脫離Pool管理),調用者可以在獲得"對象"引用后即可使用,且需要在使用結束后"歸還".如下為偽代碼:
- public Object borrowObject() throws Exception {
- Object value = null;
- synchronized (this) {
- if(!_pool.isEmpty()){
- value = _pool.remove();
- }
- }
- for(;;) {
- //如果Pool中沒有"對象",則根據相應的"耗盡"策略
- if(value == null) {
- switch(whenExhaustedAction) {
- //如果耗盡,仍繼續創建新"對象"
- case WHEN_EXHAUSTED_GROW:
- value = _factory.makeObject();
- break;
- //如果耗盡,則終止,此時以異常的方式退出.
- case WHEN_EXHAUSTED_FAIL:
- throw new NoSuchElementException("Pool exhausted");
- //如果耗盡,則阻塞,直到有"對象"歸還
- case WHEN_EXHAUSTED_BLOCK:
- try {
- synchronized (value) {
- if (value == null) {
- //maxWait為Config中指定的"最大等待時間"
- if(maxWait <= 0) {
- latch.wait();
- } else {
- latch.wait(waitTime);
- }
- } else {
- break;
- }
- }
- } catch(InterruptedException e) {
- //
- break;
- }
- default://
- }
- }
- try {
- _factory.activateObject(latch.getPair().value);
- if(_testOnBorrow &&
- !_factory.validateObject(latch.getPair().value)) {
- throw new Exception("ValidateObject failed");
- }
- return value;
- }
- catch (Throwable e) {
- try {
- _factory.destroyObject(latch.getPair().value);
- } catch (Throwable e2) {
- //
- }
- }
- }
- }
- void returnObject(Object obj) : "歸還"對象,當"對象"使用結束后,需要歸還到Pool中,才能維持Pool中對象的數量可控,如果不歸還到Pool,那么將意味著在Pool之外,將有大量的"對象"存在,那么就使用了"對象池"的意義.如下為偽代碼:
- public void returnObject(Object obj) throws Exception {
- try {
- boolean success = true;//
- if(_testOnReturn && !(_factory.validateObject(obj))) {
- success = false;
- } else {
- _factory.passivateObject(obj);
- }
- synchronized (this) {
- //檢測pool中已經空閑的對象個數是否達到閥值.
- if((_maxIdle >= 0) && (_pool.size() >= _maxIdle)) {
- success = false;
- } else if(success) {
- _pool.addFirst(new ObjectTimestampPair(obj));
- }
- }
- // Destroy the instance if necessary
- if(!success) {
- try {
- _factory.destroyObject(obj);
- } catch(Exception e) {
- // ignored
- }
- }
- } catch (Exception e) {
- //
- }
- }
- void invalidateObject(Object obj) : 銷毀對象,直接調用ObjectFactory.destroyObject(obj);.
- void addObject() : 開發者可以直接調用addObject方法用于直接創建一個"對象"并添加到Pool中.
四.代碼實例.
本實例主要用來演示一個"TCP連接池".
1) ConnectionPoolFactory.java:
- import org.apache.commons.pool.BasePoolableObjectFactory;
- import org.apache.commons.pool.impl.GenericObjectPool;
- import org.apache.commons.pool.impl.GenericObjectPool.Config;
- public class ConnectionPoolFactory {
- private GenericObjectPool pool;
- public ConnectionPoolFactory(Config config,String ip,int port){
- ConnectionFactory factory = new ConnectionFactory(ip, port);
- pool = new GenericObjectPool(factory, config);
- }
- public Socket getConnection() throws Exception{
- return (Socket)pool.borrowObject();
- }
- public void releaseConnection(Socket socket){
- try{
- pool.returnObject(socket);
- }catch(Exception e){
- if(socket != null){
- try{
- socket.close();
- }catch(Exception ex){
- //
- }
- }
- }
- }
- /**
- * inner
- * @author qing
- *
- */
- class ConnectionFactory extends BasePoolableObjectFactory {
- private InetSocketAddress address;
- public ConnectionFactory(String ip,int port){
- address = new InetSocketAddress(ip, port);
- }
- @Override
- public Object makeObject() throws Exception {
- Socket socket = new Socket();
- socket.connect(address);
- return socket;
- }
- public void destroyObject(Object obj) throws Exception {
- if(obj instanceof Socket){
- ((Socket)obj).close();
- }
- }
- public boolean validateObject(Object obj) {
- if(obj instanceof Socket){
- Socket socket = ((Socket)obj);
- if(!socket.isConnected()){
- return false;
- }
- if(socket.isClosed()){
- return false;
- }
- return true;
- }
- return false;
- }
- }
- }
2) TestMain.java(測試類):
- public class TestMain {
- /**
- * @param args
- */
- public static void main(String[] args) {
- Config config = new Config();
- config.maxActive = 16;
- config.maxWait = 30000;
- ConnectionPoolFactory poolFactory = new ConnectionPoolFactory(config, "127.0.0.1", 8011);
- Socket socket = null ;
- try{
- socket = poolFactory.getConnection();
- ////
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- if(socket != null){
- poolFactory.releaseConnection(socket);
- }
- }
- }
- }