最近在做一個內(nèi)部測試工具類的優(yōu)化工作中接觸到了連接池, 對象池技術(shù), 將原有的未使用連接池的數(shù)據(jù)庫訪問操作改成連接池方式.性能有了非常大的提升, 事實證明, 經(jīng)過兩次改造, 原來一個比較大的測試類需要500多秒, 第一次優(yōu)化后只需要300多秒, 第二次改用連接池之后同一個測試類只需要80多秒.下面是改造過程中的一些總結(jié).
對象池就是以"空間換時間"的一種常用緩存機制, 這里的"時間"特指創(chuàng)建時間,因此這也給出了對象池的適用范圍:如果一種對象的創(chuàng)建過程非常耗時的話, 那么請使用對象池. 內(nèi)部原理簡單的說, 就是將創(chuàng)建的對象放到一個容器中, 用完之后不是銷毀而是再放回該容器, 讓其他的對象調(diào)用, 對象池中還涉及到一些高級的技術(shù), 比如過期銷毀, 被破壞時銷毀, 對象數(shù)超過池大小銷毀, 對象池中沒有可用空閑對象時等待等等. apache的common-pool工具庫是對池化技術(shù)原理的一種具體實現(xiàn). 在闡述原來之前, 這里先理解幾個概念: 對象池(ObjectPool接口): 可以把它認為是一種容器, 它是用來裝池對象的, 并且包含了用來創(chuàng)建池對象的工廠對象 池對象:就是要放到池容器中的對象, 理論上可以是任何對象. 對象池工廠(ObjectPoolFactory接口):用來創(chuàng)建對象池的工廠, 這個沒什么好說的. 池對象工廠(PoolableObjectFactory接口):用來創(chuàng)建池對象, 將不用的池對象進行鈍化(passivateObject), 對要使用的池對象進行激活(activeObject), 對池對象進行驗證(validateObject), 對有問題的池對象進行銷毀(destroyObject)等工作 對象池中封裝了創(chuàng)建, 獲取, 歸還, 銷毀池對象的職責, 當然這些工作都是通過池對象工廠來實施的, 容器內(nèi)部還有一個或多個用來盛池對象的容器.對象池會對容器大小, 存放時間, 訪問等待時間, 空閑時間等等進行一些控制, 因為可以根據(jù)需要來調(diào)整這些設(shè)置. 當需要拿一個池對象的時候, 就從容器中取出一個, 如果容器中沒有的話, 而且又沒有達到容器的最大限制, 那么就調(diào)用池對象工廠, 新建一個池對象, 并調(diào)用工廠的激活方法, 對創(chuàng)建的對象進行激活, 驗證等一系列操作. 如果已經(jīng)達到池容器的最大值, 而對象池中又經(jīng)沒有空閑的對象, 那么將會繼續(xù)等待, 直到有新的空閑的對象被丟進來, 當然這個等待也是有限度的, 如果超出了這個限度, 對象池就會拋出異常. "出來混, 總是要還的", 池對象也是如此, 當將用完的池對象歸還到對象池中的時候, 對象池會調(diào)用池對象工廠對該池對象進行驗證, 如果驗證不通過則被認為是有問題的對象, 將會被銷毀, 同樣如果容器已經(jīng)滿了, 這個歸還池對象將變的"無家可歸", 也會被銷毀, 如果不屬于上面兩種情況, 對象池就會調(diào)用工廠對象將其鈍化并放入容器中. 在整個過程中, 激活, 檢查, 鈍化處理都不是必須的, 因此我們在實現(xiàn)PoolableObjectFactory接口的時候, 一般不作處理, 給空實現(xiàn)即可, 所以誕生了BasePoolableObjectFactory. 當然你也可以將要已有的對象創(chuàng)建好, 然后通過addObject放到對象池中去, 以備后用. 為了確保對對象池的訪問都是線程安全的, 所有對容器的操作都必須放在synchronized中. 在apache的common-pool工具庫中有5種對象池:GenericObjectPool和GenericKeyedObjectPool, SoftReferenceObjectPool, StackObjectPool, StackKeyedObjectPool. 五種對象池可分為兩類, 一類是無key的: ![]() 另一類是有key的: ![]() 前面兩種用CursorableLinkedList來做容器, SoftReferenceObjectPool用ArrayList做容器, 一次性創(chuàng)建所有池化對象, 并對容器中的對象進行了軟引用(SoftReference)處理, 從而保證在內(nèi)存充足的時候池對象不會輕易被jvm垃圾回收, 從而具有很強的緩存能力. 最后兩種用Stack做容器. 不帶key的對象池是對前面池技術(shù)原理的一種簡單實現(xiàn), 帶key的相對復雜一些, 它會將池對象按照key來進行分類, 具有相同的key被劃分到一組類別中, 因此有多少個key, 就會有多少個容器. 之所以需要帶key的這種對象池, 是因為普通的對象池通過makeObject()方法創(chuàng)建的對象基本上都是一模一樣的, 因為沒法傳遞參數(shù)來對池對象進行定制. 因此四種池對象的區(qū)別主要體現(xiàn)在內(nèi)部的容器的區(qū)別, Stack遵循"后進先出"的原則并能保證線程安全, CursorableLinkedList是一個內(nèi)部用游標(cursor)來定位當前元素的雙向鏈表, 是非線程安全的, 但是能滿足對容器的并發(fā)修改.ArrayList是非線程安全的, 便利方便的容器. 使用對象池的一般步驟:創(chuàng)建一個池對象工廠, 將該工廠注入到對象池中, 當要取池對象, 調(diào)用borrowObject, 當要歸還池對象時, 調(diào)用returnObject, 銷毀池對象調(diào)用clear(), 如果要連池對象工廠也一起銷毀, 則調(diào)用close(). 下面是一些時序圖: borrowObject: ![]() returnObject: ![]() invalidateObject: ![]() apache的連接池工具庫common-dbcp是common-pool在數(shù)據(jù)庫訪問方面的一個具體應用.當對common-pool熟悉之后, 對common-dbcp就很好理解了. 它通過對已有的Connection, Statment對象包裝成池對象PoolableConnection, PoolablePreparedStatement. 然后在這些池化的對象中, 持有一個對對象池的引用, 在關(guān)閉的時候, 不進行真正的關(guān)閉處理, 而是通過調(diào)用:
_pool.returnObject(this); 或:
_pool.returnObject(_key,this); 這樣一句, 將連接對象放回連接池中. 而對應的對象池前者采用的是ObjectPool, 后者是KeyedObjectPool, 因為一個數(shù)據(jù)庫只對應一個連接, 而執(zhí)行操作的Statement卻根據(jù)Sql的不同會分很多種. 因此需要根據(jù)sql語句的不同多次進行緩存 在對連接池的管理上, common-dbcp主要采用兩種對象: 一個是PoolingDriver, 另一個是PoolingDataSource, 二者的區(qū)別是PoolingDriver是一個更底層的操作類, 它持有一個連接池映射列表, 一般針對在一個jvm中要連接多個數(shù)據(jù)庫, 而后者相對簡單一些. 內(nèi)部只能持有一個連接池, 即一個數(shù)據(jù)源對應一個連接池. 下面是common-dbcp的結(jié)構(gòu)關(guān)系: ![]() 下面是參考了common-dbcp的例子之后寫的一個從連接池中獲取連接的工具類
/** * 創(chuàng)建連接 * * @since 2009-1-22 下午02:58:35 */ public class ConnectionUtils { // 一些common-dbcp內(nèi)部定義的protocol private static final String POOL_DRIVER_KEY = "jdbc:apache:commons:dbcp:"; private static final String POLLING_DRIVER = "org.apache.commons.dbcp.PoolingDriver"; /** * 取得池化驅(qū)動器 * * @return * @throws ClassNotFoundException * @throws SQLException */ private static PoolingDriver getPoolDriver() throws ClassNotFoundException, SQLException { Class.forName(POLLING_DRIVER); return (PoolingDriver) DriverManager.getDriver(POOL_DRIVER_KEY); } /** * 銷毀所有連接 * * @throws Exception */ public static void destory() throws Exception { PoolingDriver driver = getPoolDriver(); String[] names = driver.getPoolNames(); for (String name : names) { driver.getConnectionPool(name).close(); } } /** * 從連接池中獲取數(shù)據(jù)庫連接 */ public static Connection getConnection(TableMetaData table) throws Exception { String key = table.getConnectionKey(); PoolingDriver driver = getPoolDriver(); ObjectPool pool = null; // 這里找不到連接池會拋異常, 需要catch一下 try { pool = driver.getConnectionPool(key); } catch (Exception e) { } if (pool == null) { // 根據(jù)數(shù)據(jù)庫類型構(gòu)建連接工廠 ConnectionFactory connectionFactory = null; if (table.getDbAddr() != null && TableMetaData.DB_TYPE_MYSQL == table.getDbType()) { Class.forName(TableMetaData.MYSQL_DRIVER); connectionFactory = new DriverManagerConnectionFactory(table .getDBUrl(), null); } else { Class.forName(TableMetaData.ORACLE_DRIVER); connectionFactory = new DriverManagerConnectionFactory(table .getDBUrl(), table.getDbuser(), table.getDbpass()); } // 構(gòu)造連接池 ObjectPool connectionPool = new GenericObjectPool(null); new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, true); // 將連接池注冊到driver中 driver.registerPool(key, connectionPool); } // 從連接池中拿一個連接 return DriverManager.getConnection(POOL_DRIVER_KEY + key); } } 雖然對象池技術(shù)在實際開發(fā)過程中用的不是很多, 但是理解之后對我們寫程序還是有莫大的好處的, 至少我是這樣的 |
|