使用Jakarta Commons Pool處理對象池化
恰當?shù)厥褂脤ο蟪鼗夹g,可以有效地減少對象生成和初始化時的消耗,提高系統(tǒng)的運行效率。Jakarta Commons Pool組件提供了一整套用于實現(xiàn)對象池化的框架,以及若干種各具特色的對象池實現(xiàn),可以有效地減少處理對象池化時的工作量,為其它重要的工作留下更多的精力和時間。
創(chuàng)建新的對象并初始化的操作,可能會消耗很多的時間。在這種對象的初始化工作包含了一些費時的操作(例如,從一臺位于20,000千米以外的主機上讀出一些數(shù)據(jù))的時候,尤其是這樣。在需要大量生成這樣的對象的時候,就可能會對性能造成一些不可忽略的影響。要緩解這個問題,除了選用更好的硬件和更棒的虛擬機以外,適當?shù)夭捎靡恍┠軌驕p少對象創(chuàng)建次數(shù)的編碼技巧,也是一種有效的對策。對象池化技術(Object Pooling)就是這方面的著名技巧,而Jakarta Commons Pool組件則是處理對象池化的得力外援。
對象池化的基本思路是:將用過的對象保存起來,等下一次需要這種對象的時候,再拿出來重復使用,從而在一定程度上減少頻繁創(chuàng)建對象所造成的開銷。用于充當保存對象的“容器”的對象,被稱為“對象池”(Object Pool,或簡稱Pool)。
對于沒有狀態(tài)的對象(例如String),在重復使用之前,無需進行任何處理;對于有狀態(tài)的對象(例如StringBuffer),在重復使用之前,就需要把它們恢復到等同于剛剛生成時的狀態(tài)。由于條件的限制,恢復某個對象的狀態(tài)的操作不可能實現(xiàn)了的話,就得把這個對象拋棄,改用新創(chuàng)建的實例了。
并非所有對象都適合拿來池化――因為維護對象池也要造成一定開銷。對生成時開銷不大的對象進行池化,反而可能會出現(xiàn)“維護對象池的開銷”大于“生成新對象的開銷”,從而使性能降低的情況。但是對于生成時開銷可觀的對象,池化技術就是提高性能的有效策略了。
![]() |
|
![]() ![]() |
![]()
|
Jakarta Commons Pool是一個用于在Java程序中實現(xiàn)對象池化的組件。它的基本情況是:
- 主要作者:Morgan Delagrange、Geir Magnusson、Craig McClanahan、Rodney Waldhoff、David Weinrich和Dirk Verbeeck
- 最新版本:1.1
- 所含包數(shù):2個(org.apache.commons.pool和org.apache.commons.pool.impl)
- 所含類數(shù):21個(其中有4個抽象類和6個接口)
- 適用平臺:Java 2, Standard Edition.
- 單純地使用Pool組件不需要太多的Java 2的知識和經(jīng)驗,對語法和基本概念(對象、異常、類、接口、實例、繼承和實現(xiàn)等)有一般了解即可。
![]() ![]() |
![]()
|
為了順利的按照本文中提到的方法使用Pool組件,除去Java 2 SDK外,還需要先準備下列一些東西:
- Jakarta Commons Pool
- 所需版本:1.0.1+
- 下載地址: http://jakarta.apache.org/commons/pool
- 作用:處理對象池化
- Jakarta Commons Collections
- 所需版本:2.1+
- 下載地址: http://jakarta.apache.org/commons/collections
- 作用:支持Jakarta Commons Pool的運行
以上兩種軟件均有已編譯包和源代碼包兩種形式可供選擇。一般情況下,使用已編譯包即可。不過建議同時也下載源代碼包,作為參考資料使用。
如果打算使用源代碼包自行編譯,那么還需要準備以下一些東西:
- Ant
- 所需版本:1.5.3+
- 下載地址: http://ant.apache.org
- 作用:運行編譯用腳本
- JUnit
- 所需版本:3.8.1+
- 下載地址: http://www.junit.org
- 作用:編譯和運行單元測試
具體的編譯方法,可以參看有關的Ant文檔。
將解壓或編譯后得到的commons-pool.jar和commons-collections.jar放入CLASSPATH,就可以開始使用Pool組件了。
![]() ![]() |
![]()
|
PoolableObjectFactory、ObjectPool和ObjectPoolFactory
在Pool組件中,對象池化的工作被劃分給了三類對象:
- PoolableObjectFactory用于管理被池化的對象的產(chǎn)生、激活、掛起、校驗和銷毀;
- ObjectPool用于管理要被池化的對象的借出和歸還,并通知PoolableObjectFactory完成相應的工作;
- ObjectPoolFactory則用于大量生成相同類型和設置的ObjectPool。
相應地,使用Pool組件的過程,也大體可以劃分成“創(chuàng)立PoolableObjectFactory”、“使用ObjectPool”和可選的“利用ObjectPoolFactory”三種動作。
![]() ![]() |
![]()
|
創(chuàng)立PoolableObjectFactory
Pool組件利用PoolableObjectFactory來照看被池化的對象。ObjectPool的實例在需要處理被池化的對象的產(chǎn)生、激活、掛起、校驗和銷毀工作時,就會調(diào)用跟它關聯(lián)在一起的PoolableObjectFactory實例的相應方法來操作。
PoolableObjectFactory是在org.apache.commons.pool包中定義的一個接口。實際使用的時候需要利用這個接口的一個具體實現(xiàn)。Pool組件本身沒有包含任何一種PoolableObjectFactory實現(xiàn),需要根據(jù)情況自行創(chuàng)立。
創(chuàng)立PoolableObjectFactory的大體步驟是:
- 創(chuàng)建一個實現(xiàn)了PoolableObjectFactory接口的類。
import org.apache.commons.pool.PoolableObjectFactory; public class PoolableObjectFactorySample implements PoolableObjectFactory { private static int counter = 0; }
- 為這個類添加一個Object makeObject()方法。這個方法用于在必要時產(chǎn)生新的對象。
public Object makeObject() throws Exception { Object obj = String.valueOf(counter++); System.err.println("Making Object " + obj); return obj; }
- 為這個類添加一個void activateObject(Object obj)方法。這個方法用于將對象“激活”――設置為適合開始使用的狀態(tài)。
public void activateObject(Object obj) throws Exception { System.err.println("Activating Object " + obj); }
- 為這個類添加一個void passivateObject(Object obj)方法。這個方法用于將對象“掛起”――設置為適合開始休眠的狀態(tài)。
public void passivateObject(Object obj) throws Exception { System.err.println("Passivating Object " + obj); }
- 為這個類添加一個boolean validateObject(Object obj)方法。這個方法用于校驗一個具體的對象是否仍然有效,已失效的對象會被自動交給destroyObject方法銷毀
public boolean validateObject(Object obj) { boolean result = (Math.random() > 0.5); System.err.println("Validating Object " + obj + " : " + result); return result; }
- 為這個類添加一個void destroyObject(Object obj)方法。這個方法用于銷毀被validateObject判定為已失效的對象。
public void destroyObject(Object obj) throws Exception { System.err.println("Destroying Object " + obj); }
最后完成的PoolableObjectFactory類似這個樣子:
|
|
![]() ![]() |
![]()
|
有了合適的PoolableObjectFactory之后,便可以開始請出ObjectPool來與之同臺演出了。
ObjectPool是在org.apache.commons.pool包中定義的一個接口,實際使用的時候也需要利用這個接口的一個具體實現(xiàn)。Pool組件本身包含了若干種現(xiàn)成的ObjectPool實現(xiàn),可以直接利用。如果都不合用,也可以根據(jù)情況自行創(chuàng)建。具體的創(chuàng)建方法,可以參看Pool組件的文檔和源碼。
ObjectPool的使用方法類似這樣:
- 生成一個要用的PoolableObjectFactory類的實例。
PoolableObjectFactory factory = new PoolableObjectFactorySample();
- 利用這個PoolableObjectFactory實例為參數(shù),生成一個實現(xiàn)了ObjectPool接口的類(例如StackObjectPool)的實例,作為對象池。
ObjectPool pool = new StackObjectPool(factory);
- 需要從對象池中取出對象時,調(diào)用該對象池的Object borrowObject()方法。
Object obj = null; obj = pool.borrowObject();
- 需要將對象放回對象池中時,調(diào)用該對象池的void returnObject(Object obj)方法。
pool.returnObject(obj);
- 當不再需要使用一個對象池時,調(diào)用該對象池的void close()方法,釋放它所占據(jù)的資源。
pool.close();
這些操作都可能會拋出異常,需要另外處理。
比較完整的使用ObjectPool的全過程,可以參考這段代碼:
|
|
另外,ObjectPool接口還定義了幾個可以由具體的實現(xiàn)決定要不要支持的操作,包括:
void clear()
清除所有當前在此對象池中休眠的對象。
int getNumActive()
返回已經(jīng)從此對象池中借出的對象的總數(shù)。
int getNumIdle()
返回當前在此對象池中休眠的對象的數(shù)目。
void setFactory(PoolableObjectFactory factory)
將當前對象池與參數(shù)中給定的PoolableObjectFactory相關聯(lián)。如果在當前狀態(tài)下,無法完成這一操作,會有一個IllegalStateException異常拋出。
![]() ![]() |
![]()
|
有時候,要在多處生成類型和設置都相同的ObjectPool。如果在每個地方都重寫一次調(diào)用相應構(gòu)造方法的代碼,不但比較麻煩,而且日后修改起來,也有所不便。這種時候,正是使用ObjectPoolFactory的時機。
ObjectPoolFactory是一個在org.apache.commons.pool中定義的接口,它定義了一個稱為ObjectPool createPool()方法,可以用于大量生產(chǎn)類型和設置都相同的ObjectPool。
Pool組件中,對每一個ObjectPool實現(xiàn),都有一個對應的ObjectPoolFactory實現(xiàn)。它們相互之間,有一一對應的參數(shù)相同的構(gòu)造方法。使用的時候,只要先用想要的參數(shù)和想用的ObjectPoolFactory實例,構(gòu)造出一個ObjectPoolFactory對象,然后在需要生成ObjectPool的地方,調(diào)用這個對象的createPool()方法就可以了。日后無論想要調(diào)整所用ObjectPool的參數(shù)還是類型,只需要修改這一處,就可以大功告成了。
將 《使用ObjectPool》一節(jié)中的例子,改為使用ObjectPoolFactory來生成所用的ObjectPool對象之后,基本就是這種形式:
|
|
![]() ![]() |
![]()
|
PoolableObjectFactory定義了許多方法,可以適應多種不同的情況。但是,在并沒有什么特殊需要的時候,直接實現(xiàn)PoolableObjectFactory接口,就要編寫若干的不進行任何操作,或是始終返回true的方法來讓編譯通過,比較繁瑣。這種時候就可以借助BasePoolableObjectFactory的威力,來簡化編碼的工作。
BasePoolableObjectFactory是org.apache.commons.pool包中的一個抽象類。它實現(xiàn)了PoolableObjectFactory接口,并且為除了makeObject之外的方法提供了一個基本的實現(xiàn)――activateObject、passivateObject和destroyObject不進行任何操作,而validateObject始終返回true。通過繼承這個類,而不是直接實現(xiàn)PoolableObjectFactory接口,就可以免去編寫一些只起到讓編譯通過的作用的代碼的麻煩了。
這個例子展示了一個從BasePoolableObjectFactory擴展而來的PoolableObjectFactory:
|
|
![]() ![]() |
![]()
|
可口可樂公司的軟飲料有可口可樂、雪碧和芬達等品種,百事可樂公司的軟飲料有百事可樂、七喜和美年達等類型,而Pool組件提供的ObjectPool實現(xiàn)則有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等種類。
不同類型的軟飲料各有各自的特點,分別適應不同消費者的口味;而不同類型的ObjectPool也各有各自的特色,分別適應不同的情況。
![]() ![]() |
![]()
|
StackObjectPool利用一個java.util.Stack對象來保存對象池里的對象。這種對象池的特色是:
- 可以為對象池指定一個初始的參考大小(當空間不夠時會自動增長)。
- 在對象池已空的時候,調(diào)用它的borrowObject方法,會自動返回新創(chuàng)建的實例。
- 可以為對象池指定一個可保存的對象數(shù)目的上限。達到這個上限之后,再向池里送回的對象會被自動送去回收。
StackObjectPool的構(gòu)造方法共有六個,其中:
- 最簡單的一個是StackObjectPool(),一切采用默認的設置,也不指明要用的PoolableObjectFactory實例。
- 最復雜的一個則是StackObjectPool(PoolableObjectFactory factory, int max, int init)。其中:
- 參數(shù)factory指明要與之配合使用的PoolableObjectFactory實例;
- 參數(shù)max設定可保存對象數(shù)目的上限;
- 參數(shù)init則指明初始的參考大小。
- 剩余的四個構(gòu)造方法則是最復雜的構(gòu)造方法在某方面的簡化版本,可以根據(jù)需要選用。它們是:
- StackObjectPool(int max)
- StackObjectPool(int max, int init)
- StackObjectPool(PoolableObjectFactory factory)
- StackObjectPool(PoolableObjectFactory factory, int max)
用不帶factory參數(shù)的構(gòu)造方法構(gòu)造的StackObjectPool實例,必須要在用它的setFactory(PoolableObjectFactory factory)方法與某一PoolableObjectFactory實例關聯(lián)起來后才能正常使用。
這種對象池可以在沒有Jakarta Commmons Collections組件支持的情況下正常運行。
![]() ![]() |
![]()
|
SoftReferenceObjectPool利用一個java.util.ArrayList對象來保存對象池里的對象。不過它并不在對象池里直接保存對象本身,而是保存它們的“軟引用”(Soft Reference)。這種對象池的特色是:
- 可以保存任意多個對象,不會有容量已滿的情況發(fā)生。
- 在對象池已空的時候,調(diào)用它的borrowObject方法,會自動返回新創(chuàng)建的實例。
- 可以在初始化同時,在池內(nèi)預先創(chuàng)建一定量的對象。
- 當內(nèi)存不足的時候,池中的對象可以被Java虛擬機回收。
SoftReferenceObjectPool的構(gòu)造方法共有三個,其中:
- 最簡單的是SoftReferenceObjectPool(),不預先在池內(nèi)創(chuàng)建對象,也不指明要用的PoolableObjectFactory實例。
- 最復雜的一個則是SoftReferenceObjectPool(PoolableObjectFactory factory, int initSize)。其中:
- 參數(shù)factory指明要與之配合使用的PoolableObjectFactory實例
- 參數(shù)initSize則指明初始化時在池中創(chuàng)建多少個對象。
- 剩下的一個構(gòu)造方法,則是最復雜的構(gòu)造方法在某方面的簡化版本,適合在大多數(shù)情況下使用。它是:
- SoftReferenceObjectPool(PoolableObjectFactory factory)
用不帶factory參數(shù)的構(gòu)造方法構(gòu)造的SoftReferenceObjectPool實例,也要在用它的setFactory(PoolableObjectFactory factory)方法與某一PoolableObjectFactory實例關聯(lián)起來后才能正常使用。
這種對象池也可以在沒有Jakarta Commmons Collections組件支持的情況下正常運行。
![]() ![]() |
![]()
|
GenericObjectPool利用一個org.apache.commons.collections.CursorableLinkedList對象來保存對象池里的對象。這種對象池的特色是:
- 可以設定最多能從池中借出多少個對象。
- 可以設定池中最多能保存多少個對象。
- 可以設定在池中已無對象可借的情況下,調(diào)用它的borrowObject方法時的行為,是等待、創(chuàng)建新的實例還是拋出異常。
- 可以分別設定對象借出和還回時,是否進行有效性檢查。
- 可以設定是否使用一個單獨的線程,對池內(nèi)對象進行后臺清理。
GenericObjectPool的構(gòu)造方法共有七個,其中:
- 最簡單的一個是GenericObjectPool(PoolableObjectFactory factory)。僅僅指明要用的PoolableObjectFactory實例,其它參數(shù)則采用默認值。
- 最復雜的一個是GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, boolean testWhileIdle)。其中:
- 參數(shù)factory指明要與之配合使用的PoolableObjectFactory實例。
- 參數(shù)maxActive指明能從池中借出的對象的最大數(shù)目。如果這個值不是正數(shù),表示沒有限制。
- 參數(shù)whenExhaustedAction指定在池中借出對象的數(shù)目已達極限的情況下,調(diào)用它的borrowObject方法時的行為。可以選用的值有:
- GenericObjectPool.WHEN_EXHAUSTED_BLOCK,表示等待;
- GenericObjectPool.WHEN_EXHAUSTED_GROW,表示創(chuàng)建新的實例(不過這就使maxActive參數(shù)失去了意義);
- GenericObjectPool.WHEN_EXHAUSTED_FAIL,表示拋出一個java.util.NoSuchElementException異常。
- 參數(shù)maxWait指明若在對象池空時調(diào)用borrowObject方法的行為被設定成等待,最多等待多少毫秒。如果等待時間超過了這個數(shù)值,則會拋出一個java.util.NoSuchElementException異常。如果這個值不是正數(shù),表示無限期等待。
- 參數(shù)testOnBorrow設定在借出對象時是否進行有效性檢查。
- 參數(shù)testOnBorrow設定在還回對象時是否進行有效性檢查。
- 參數(shù)timeBetweenEvictionRunsMillis,設定間隔每過多少毫秒進行一次后臺對象清理的行動。如果這個值不是正數(shù),則實際上不會進行后臺對象清理。
- 參數(shù)numTestsPerEvictionRun,設定在進行后臺對象清理時,每次檢查幾個對象。如果這個值不是正數(shù),則每次檢查的對象數(shù)是檢查時池內(nèi)對象的總數(shù)乘以這個值的負倒數(shù)再向上取整的結(jié)果――也就是說,如果這個值是-2(-3、-4、-5……)的話,那么每次大約檢查當時池內(nèi)對象總數(shù)的1/2(1/3、1/4、1/5……)左右。
- 參數(shù)minEvictableIdleTimeMillis,設定在進行后臺對象清理時,視休眠時間超過了多少毫秒的對象為過期。過期的對象將被回收。如果這個值不是正數(shù),那么對休眠時間沒有特別的約束。
- 參數(shù)testWhileIdle,則設定在進行后臺對象清理時,是否還對沒有過期的池內(nèi)對象進行有效性檢查。不能通過有效性檢查的對象也將被回收。
- 另一個比較特別的構(gòu)造方法是GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 。其中:
- 參數(shù)factory指明要與之配合使用的PoolableObjectFactory實例;
- 參數(shù)config則指明一個包括了各個參數(shù)的預設值的對象(詳見《GenericObjectPool.Config》一節(jié))。
- 剩下的五個構(gòu)造函數(shù)則是最復雜的構(gòu)造方法在某方面的簡化版本,可以根據(jù)情況選用。它們是:
- GenericObjectPool(PoolableObjectFactory factory, int maxActive)
- GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait)
- GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, boolean testOnBorrow, boolean testOnReturn)
- GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle)
- GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn)
這種對象池不可以在沒有Jakarta Commmons Collections組件支持的情況下運行。
![]() ![]() |
![]()
|
調(diào)用一個有很多的參數(shù)的方法的時候,很可能將參數(shù)的位置和個數(shù)搞錯,導致編譯或運行時的錯誤;閱讀包含了有很多參數(shù)的方法調(diào)用的代碼的時候,也很可能因為沒有搞對參數(shù)的位置和個數(shù),產(chǎn)生錯誤的理解。因此,人們往往避免給一個方法安排太多的參數(shù)的做法(所謂的“Long Parameter List”)。不過,有些方法又確實需要許多參數(shù)才能完成工作。于是,就有人想到了一種將大批的參數(shù)封裝到一個對象(稱為參數(shù)對象,Parameter Object)里,然后將這個對象作為單一的參數(shù)傳遞的兩全其美的對策。
因為生成GenericKeyedObjectPool時可供設置的特性非常之多,所以它的構(gòu)造方法里也就難免會需要不少的參數(shù)。GenericKeyedObjectPool除了提供了幾個超長的構(gòu)造方法之外,同時也定義了一個使用參數(shù)對象的構(gòu)造方法。所用參數(shù)對象的類型是GenericKeyedObjectPool.Config。
GenericKeyedObjectPool.Config定義了許多的public字段,每個對應一種可以為GenericKeyedObjectPool設置的特性,包括:
- int maxActive
- int maxIdle
- long maxWait
- long minEvictableIdleTimeMillis
- int numTestsPerEvictionRun
- boolean testOnBorrow
- boolean testOnReturn
- boolean testWhileIdle
- long timeBetweenEvictionRunsMillis
- byte whenExhaustedAction
這些字段的作用,與在GenericKeyedObjectPool最復雜的構(gòu)造方法中與它們同名的參數(shù)完全相同。
使用的時候,先生成一個GenericKeyedObjectPool.Config對象,然后將個字段設置為想要的值,最后用這個對象作為唯一的參數(shù)調(diào)用GenericKeyedObjectPool的構(gòu)造方法即可。
注意:使用有許多public字段、卻沒有任何方法的類,也是一個人們往往加以避免的行為(所謂的“Data Class”)。不過這次GenericKeyedObjectPool特立獨行了一回。
![]() ![]() |
![]()
|
有時候,單用對池內(nèi)所有對象一視同仁的對象池,并不能解決的問題。例如,對于一組某些參數(shù)設置不同的同類對象――比如一堆指向不同地址的java.net.URL對象或者一批代表不同語句的java.sql.PreparedStatement對象,用這樣的方法池化,就有可能取出不合用的對象的麻煩。
可以通過為每一組參數(shù)相同的同類對象建立一個單獨的對象池來解決這個問題。但是,如果使用普通的ObjectPool來實施這個計策的話,因為普通的PoolableObjectFactory只能生產(chǎn)出大批設置完全一致的對象,就需要為每一組參數(shù)相同的對象編寫一個單獨的PoolableObjectFactory,工作量相當可觀。這種時候就適合調(diào)遣Pool組件中提供的一種“帶鍵值的對象池”來展開工作了。
Pool組件采用實現(xiàn)了KeyedObjectPool接口的類,來充當帶鍵值的對象池。相應的,這種對象池需要配合實現(xiàn)了KeyedPoolableObjectFactory接口的類和實現(xiàn)了KeyedObjectPoolFactory接口的類來使用(這三個接口都在org.apache.commons.pool包中定義):
- KeyedPoolableObjectFactory和PoolableObjectFactory形式如出一轍,只是每個方法都增加了一個Object key參數(shù)而已:
- makeObject的參數(shù)變?yōu)?Object key)
- activateObject的參數(shù)變?yōu)?Object key, Object obj)
- passivateObject的參數(shù)變?yōu)?Object key, Object obj)
- validateObject的參數(shù)變?yōu)镺bject key, Object obj)
- destroyObject的參數(shù)變?yōu)?Object key, Object obj)
另外Pool組件也提供了BaseKeyedPoolableObjectFactory,用于充當和BasePoolableObjectFactory差不多的角色。
- KeyedObjectPool和ObjectPool的形式大同小異,只是某些方法的參數(shù)類型發(fā)生了變化,某些方法分成了兩種略有不同的版本:
- 用Object borrowObject(Object key)和void returnObject(Object key, Object obj)來負責對象出借和歸還的動作。
- 用void close()來關閉不再需要的對象池。
- 用void clear(Object key)和void clear()來清空池中的對象,前者針對與特定鍵值相關聯(lián)的實例,后者針對整個對象池。
- 用int getNumActive(Object key)和int getNumActive()來查詢已借出的對象數(shù),前者針對與特定鍵值相關聯(lián)的實例,后者針對整個對象池。
- 用int getNumIdle(Object key)和int getNumIdle()來查詢正在休眠的對象數(shù),前者針對與特定鍵值相關聯(lián)的實例,后者針對整個對象池。
- 用void setFactory(KeyedPoolableObjectFactory factory)來設置要用的KeyedPoolableObjectFactory實例。
void clear、int getNumActive、int getNumIdle和void setFactory的各種版本都仍然是可以由具體實現(xiàn)自行決定是否要支持的方法。如果所用的KeyedObjectPool實現(xiàn)不支持這些操作,那么調(diào)用這些方法的時候,會拋出一個UnsupportedOperationException異常。
- KeyedObjectPoolFactory和ObjectPoolFactory的形式完全相同,只是所代表的對象不同而已。
這一類對象池的基本使用方法接近于這樣:
|
|
Pool組件自帶的KeyedObjectPool的實現(xiàn)有StackKeyedObjectPool和GenericKeyedObjectPool兩種。它們的使用方法分別與它們各自的近親KeyedObjectPool和KeyedObjectPool基本一致,只是原來使用GenericObjectPool.Config的地方要使用GenericKeyedObjectPool.Config代替。
![]() ![]() |
![]()
|
Java并未提供一種機制來保證兩個方法被調(diào)用的次數(shù)之間呈現(xiàn)一種特定的關系(相等,相差一個常數(shù),或是其它任何關系)。因此,完全可以做到建立一個ObjectPool對象,然后調(diào)用一次borrowObject方法,借出一個對象,之后重復兩次returnObject方法調(diào)用,進行兩次歸還。而調(diào)用一個從不曾借出對象的ObjectPool的returnObject方法也并不是一個不可完成的任務。
盡管這些使用方法并不合乎returnObject的字面意思,但是Pool組件中的各個ObjectPool/KeyedObjectPool實現(xiàn)都不在乎這一點。它們的returnObject方法都只是單純地召喚與當前對象池關聯(lián)的PoolableObjectFactory實例,看這對象能否經(jīng)受得起validateObject的考驗而已。考驗的結(jié)果決定了這個對象是應該拿去作passivateObject處理,而后留待重用;還是應該拿去作destroyObject處理,以免占用資源。也就是說,當出借少于歸還的時候,并不會額外發(fā)生什么特別的事情(當然,有可能因為該對象池處于不接受歸還對象的請求的狀態(tài)而拋出異常,不過這是常規(guī)現(xiàn)象)。
在實際使用中,可以利用這一特性來向?qū)ο蟪貎?nèi)加入通過其它方法生成的對象。
![]() ![]() |
![]()
|
有時候可能要在多線程環(huán)境下使用Pool組件,這時候就會遇到和Pool組件的線程安全程度有關的問題。
因為ObjectPool和KeyedObjectPool都是在org.apache.commons.pool中定義的接口,而在接口中無法使用“synchronized”來修飾方法,所以,一個ObjectPool/KeyedObjectPool下的各個方法是否是同步方法,完全要看具體的實現(xiàn)。而且,單純地使用了同步方法,也并不能使對象就此在多線程環(huán)境里高枕無憂。
就Pool組件中自帶的幾個ObjectPool/KeyedObjectPool的實現(xiàn)而言,它們都在一定程度上考慮了在多線程環(huán)境中使用的情況。不過還不能說它們是完全“線程安全”的。
例如,這段代碼有些時候就會有一些奇怪的表現(xiàn),最后輸出的結(jié)果比預期的要大:
|
|
要避免這種情況,就要進一步采取一些措施才行:
|
|
基本上,可以說Pool組件是線程相容的。但是要在多線程環(huán)境中使用,還需要作一些特別的處理。
![]() ![]() |
![]()
|
采用對象池化的本意,是要通過減少對象生成的次數(shù),減少花在對象初始化上面的開銷,從而提高整體的性能。然而池化處理本身也要付出代價,因此,并非任何情況下都適合采用對象池化。
Dr. Cliff Click在JavaOne 2003上發(fā)表的《Performance Myths Exposed》中,給出了一組其它條件都相同時,使用與不使用對象池化技術的實際性能的比較結(jié)果。他的實測結(jié)果表明:
- 對于類似Point這樣的輕量級對象,進行池化處理后,性能反而下降,因此不宜池化;
- 對于類似Hashtable這樣的中量級對象,進行池化處理后,性能基本不變,一般不必池化(池化會使代碼變復雜,增大維護的難度);
- 對于類似JPanel這樣的重量級對象,進行池化處理后,性能有所上升,可以考慮池化。
根據(jù)使用方法的不同,實際的情況可能與這一測量結(jié)果略有出入。在配置較高的機器和技術較強的虛擬機上,不宜池化的對象的范圍可能會更大。不過,對于像網(wǎng)絡和數(shù)據(jù)庫連接這類重量級的對象來說,目前還是有池化的必要。
基本上,只在重復生成某種對象的操作成為影響性能的關鍵因素的時候,才適合進行對象池化。如果進行池化所能帶來的性能提高并不重要的話,還是不采用對象池化技術,以保持代碼的簡明,而使用更好的硬件和更棒的虛擬機來提高性能為佳。
posted on 2006-08-24 17:49 Binary 閱讀(247) 評論(0) 編輯 收藏 所屬分類: Apache jakarta