對於那些創建耗時較長,或者資源占用較多的對象,比如網路連接,線程之類的資源,通常使用池化來管理這些對象,從而達到提高性能的目的。比如資料庫連接池(c3p0, dbcp), java的線程池 ExecutorService.Apache Commons Pool提供一套池化規範介面,以及實現通用邏輯, ...
對於那些創建耗時較長,或者資源占用較多的對象,比如網路連接,線程之類的資源,通常使用池化來管理這些對象,從而達到提高性能的目的。比如資料庫連接池(c3p0, dbcp), java的線程池 ExecutorService.Apache Commons Pool提供一套池化規範介面,以及實現通用邏輯,我們只需要要實現其抽象出來的方法就可以。Commons Pool主要有以下幾個對象 PooledObject:這個就是前面所說需要池化的資源,被池化的對象可以抽離出共有屬性,如,創建時間,狀態,最近一次使用時間等 PooledObjectFactory: 對象工廠,負責對PooledOjbect的創建,狀態驗證,銷毀之類的工作 ObjectPool: 對象池,它是負責和對象使用者直接打交道的, 對使用者提供獲取對象,返還對象介面 英文不太好的同學可能被這幾個對象的命名搞暈,其實世間萬物的道理都是想通的。如果把圖書館的書比作PooledObject 的話,那麼圖書館就是ObjectPool,圖書館為了管理書,對書添加了入庫時間,書借存狀態等用於管理的屬性。圖書館(ObjectPool)對借書人提供借書,還書的服務(即介面)。而書(PooledObject )的印刷,質量檢驗,回收(有點不現實)等實實在在的工作還是得交給印刷廠(PooledObjectFactory )來做。其流程關係如下: 下麵看看如何使用Commons Pool如何實現自己的對象池。創建自己的對象池大致需要以下工作, 1. 首先你已經編寫好了你的資源對象(這部分不屬於池化內容),之後編寫實現apache的PooledObjectFactory<T>介面的Factory類,這是編寫自己對象池最主要的工作。你的Factory可能需要添加一些用於池化對象的初始化 ,池化對象的驗證等參數作為成員變數。 2. 編寫自己的Pool類,讓其繼承或者內部引用apache的GenericObjectPool<T>,GenericObjectPool實現了ObjectPool介面,已經封裝了對池化對象的生命周期管理邏輯 3. 可選部分,繼承apache的GenericObjectPoolConfig,重寫構造器,添加一些適合自己業務場景的初始化參數。 我們以Jedis的源碼為例,學習它的實現。我們先看下使用JedisPool操作Redis的簡單例子
package com.eg.test.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class TestPool { public static void main(String[] args) { //JedisPoolConfig繼承apache的GenericObjectPoolConfig,配置Pool的相關參數如下: JedisPoolConfig config = new JedisPoolConfig(); //如果賦值為-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態為exhausted(耗盡)。 config.setMaxTotal(500); //控制一個pool最多有多少個狀態為idle(空閑的)的jedis實例。 config.setMaxIdle(5); //表示當borrow(引入)一個jedis實例時,最大的等待時間,如果超過等待時間,則直接拋出JedisConnectionException; config.setMaxWaitMillis(30000);; //在borrow一個jedis實例時,是否提前進行validate操作;如果為true,則得到的jedis實例均是可用的; config.setTestOnBorrow(true); JedisPool pool = new JedisPool(config, "192.168.2.191", 8888); //從pool中獲取對象 Jedis jedis = pool.getResource(); String value = jedis.get("someKey"); } }
首先看JedisFactory的實現:
class JedisFactory implements PooledObjectFactory<Jedis> { private final AtomicReference<HostAndPort> hostAndPort = new AtomicReference<HostAndPort>(); private final int connectionTimeout; private final int soTimeout; //省略構造函數,都是一些初始化成員變數的操作 @Override public void activateObject(PooledObject<Jedis> pooledJedis) throws Exception { final BinaryJedis jedis = pooledJedis.getObject(); if (jedis.getDB() != database) { jedis.select(database); } } @Override public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception { final BinaryJedis jedis = pooledJedis.getObject(); if (jedis.isConnected()) { try { try { jedis.quit(); } catch (Exception e) { } jedis.disconnect(); } catch (Exception e) { } } } @Override public PooledObject<Jedis> makeObject() throws Exception { final HostAndPort hostAndPort = this.hostAndPort.get(); final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier); try { jedis.connect(); if (null != this.password) { jedis.auth(this.password); } if (database != 0) { jedis.select(database); } if (clientName != null) { jedis.clientSetname(clientName); } } catch (JedisException je) { jedis.close(); throw je; } return new DefaultPooledObject<Jedis>(jedis); } @Override public void passivateObject(PooledObject<Jedis> pooledJedis) throws Exception { // TODO maybe should select db 0? Not sure right now. } @Override public boolean validateObject(PooledObject<Jedis> pooledJedis) { final BinaryJedis jedis = pooledJedis.getObject(); try { HostAndPort hostAndPort = this.hostAndPort.get(); String connectionHost = jedis.getClient().getHost(); int connectionPort = jedis.getClient().getPort(); return hostAndPort.getHost().equals(connectionHost) && hostAndPort.getPort() == connectionPort && jedis.isConnected() && jedis.ping().equals("PONG"); } catch (final Exception e) { return false; } } }
我們看到JedisFactory代碼較少,但是邏輯很清晰。該Factory將作為ObjectPool的成員變數,其中四個重寫的方法被ObjectPool管理對象生命周期的時候調用。makeobject()方法負責創建Jedis實例,成功調用connect()方法建立有狀態的socket連接之後,返回一個包裝了jedis的DefaultPooledObject對象,DefaultPooledObject實現了關於統計池化對象狀態信息的PooledObject介面。validateObject()方法用於對對象狀態的檢驗,Jedis對象的狀態通過socket的ping-pong來驗證連接是否正常。destroyObject()方法用來銷毀對象,Jedis對象將會斷開連接,回收資源。
再看JedisPool的實現,由於JedisPool繼承Pool<T>,所以我們主要看Pool<T>的部分代碼:
public abstract class Pool<T> implements Closeable { protected GenericObjectPool<T> internalPool; public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) { initPool(poolConfig, factory); } public boolean isClosed() { return this.internalPool.isClosed(); } 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); } public T getResource() { try { return internalPool.borrowObject(); } catch (NoSuchElementException nse) { throw new JedisException("Could not get a resource from the pool", nse); } catch (Exception e) { throw new JedisConnectionException("Could not get a resource from the pool", e); } } protected void returnResourceObject(final T resource) { if (resource == null) { return; } try { internalPool.returnObject(resource); } catch (Exception e) { throw new JedisException("Could not return the resource to the pool", e); } } public void addObjects(int count) { try { for (int i = 0; i < count; i++) { this.internalPool.addObject(); } } catch (Exception e) { throw new JedisException("Error trying to add idle objects", e); } } }JedisPool通過內部引用GenericObjectPool,包裝其介面的裝飾者模式,相比繼承來說這種模式更加靈活。JedisPool的構造方法需要將JedisFactory以及JedisPoolConfig創建標準的ObjectPool作為自己的成員變數。所以pool.getResource()方法的背後還是調用PoolObject.borrowObject()。 最後我們稍微看下JedisPoolConfig,只是做了一些預初始化參數的工作。
public class JedisPoolConfig extends GenericObjectPoolConfig { public JedisPoolConfig() { // defaults to make your life with connection pool easier :) setTestWhileIdle(true); setMinEvictableIdleTimeMillis(60000); setTimeBetweenEvictionRunsMillis(30000); setNumTestsPerEvictionRun(-1); } }