內(nèi)容:
對象池化技術(shù)
Jakarta Commons Pool組件
下載和安裝
PoolableObjectFactory、ObjectPool和ObjectPoolFactory
創(chuàng)立PoolableObjectFactory
使用ObjectPool
利用ObjectPoolFactory
借助BasePoolableObjectFactory
各式各樣的ObjectPool
StackObjectPool
SoftReferenceObjectPool
GenericObjectPool
GenericObjectPool.Config
帶鍵值的對象池
當(dāng)出借少于歸還
線程安全問題
什么時(shí)候不要池化
結(jié)束語
參考資料
關(guān)于作者
對于本文的評價(jià)
孫海濤 (alexhsun@hotmail.com)
2003 年 12 月
恰當(dāng)?shù)厥褂脤ο蟪鼗夹g(shù),可以有效地減少對象生成和初始化時(shí)的消耗,提高系統(tǒng)的運(yùn)行效率。Jakarta Commons Pool組件提供了一整套用于實(shí)現(xiàn)對象池化的框架,以及若干種各具特色的對象池實(shí)現(xiàn),可以有效地減少處理對象池化時(shí)的工作量,為其它重要的工作留下更多的精力和時(shí)間。
創(chuàng)建新的對象并初始化的操作,可能會消耗很多的時(shí)間。在這種對象的初始化工作包含了一些費(fèi)時(shí)的操作(例如,從一臺位于20,000千米以外的主機(jī)上讀出一些數(shù)據(jù))的時(shí)候,尤其是這樣。在需要大量生成這樣的對象的時(shí)候,就可能會對性能造成一些不可忽略的影響。要緩解這個問題,除了選用更好的硬件和更棒的虛擬機(jī)以外,適當(dāng)?shù)夭捎靡恍┠軌驕p少對象創(chuàng)建次數(shù)的編碼技巧,也是一種有效的對策。對象池化技術(shù)(Object Pooling)就是這方面的著名技巧,而Jakarta Commons Pool組件則是處理對象池化的得力外援。
對象池化技術(shù)
對象池化的基本思路是:將用過的對象保存起來,等下一次需要這種對象的時(shí)候,再拿出來重復(fù)使用,從而在一定程度上減少頻繁創(chuàng)建對象所造成的開銷。用于充當(dāng)保存對象的“容器”的對象,被稱為“對象池”(Object Pool,或簡稱Pool)。
對于沒有狀態(tài)的對象(例如String),在重復(fù)使用之前,無需進(jìn)行任何處理;對于有狀態(tài)的對象(例如StringBuffer),在重復(fù)使用之前,就需要把它們恢復(fù)到等同于剛剛生成時(shí)的狀態(tài)。由于條件的限制,恢復(fù)某個對象的狀態(tài)的操作不可能實(shí)現(xiàn)了的話,就得把這個對象拋棄,改用新創(chuàng)建的實(shí)例了。
并非所有對象都適合拿來池化??因?yàn)榫S護(hù)對象池也要造成一定開銷。對生成時(shí)開銷不大的對象進(jìn)行池化,反而可能會出現(xiàn)“維護(hù)對象池的開銷”大于“生成新對象的開銷”,從而使性能降低的情況。但是對于生成時(shí)開銷可觀的對象,池化技術(shù)就是提高性能的有效策略了。
說明:英語中的Pool除了“池”之外,還有“供多方共享的資源”意思。作者十分懷疑第二種才是“Object Pool”中的Pool的實(shí)際含義,但是“對象池”的說法已經(jīng)廣為流傳,而一時(shí)又沒有足以替代的貼切譯法,因此這里仍然沿用這種譯名。
Jakarta Commons Pool組件
Jakarta Commons Pool是一個用于在Java程序中實(shí)現(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)驗(yàn),對語法和基本概念(對象、異常、類、接口、實(shí)例、繼承和實(shí)現(xiàn)等)有一般了解即可。
下載和安裝
為了順利的按照本文中提到的方法使用Pool組件,除去Java 2 SDK外,還需要先準(zhǔn)備下列一些東西:
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的運(yùn)行
以上兩種軟件均有已編譯包和源代碼包兩種形式可供選擇。一般情況下,使用已編譯包即可。不過建議同時(shí)也下載源代碼包,作為參考資料使用。
如果打算使用源代碼包自行編譯,那么還需要準(zhǔn)備以下一些東西:
Ant
所需版本:1.5.3+
下載地址:
http://ant.apache.org作用:運(yùn)行編譯用腳本
JUnit
所需版本:3.8.1+
下載地址:
http://www.junit.org作用:編譯和運(yùn)行單元測試
具體的編譯方法,可以參看有關(guān)的Ant文檔。
將解壓或編譯后得到的commons-pool.jar和commons-collections.jar放入CLASSPATH,就可以開始使用Pool組件了。
PoolableObjectFactory、ObjectPool和ObjectPoolFactory
在Pool組件中,對象池化的工作被劃分給了三類對象:
PoolableObjectFactory用于管理被池化的對象的產(chǎn)生、激活、掛起、校驗(yàn)和銷毀;
ObjectPool用于管理要被池化的對象的借出和歸還,并通知PoolableObjectFactory完成相應(yīng)的工作;
ObjectPoolFactory則用于大量生成相同類型和設(shè)置的ObjectPool。
相應(yīng)地,使用Pool組件的過程,也大體可以劃分成“創(chuàng)立PoolableObjectFactory”、“使用ObjectPool”和可選的“利用ObjectPoolFactory”三種動作。
創(chuàng)立PoolableObjectFactory
Pool組件利用PoolableObjectFactory來照看被池化的對象。ObjectPool的實(shí)例在需要處理被池化的對象的產(chǎn)生、激活、掛起、校驗(yàn)和銷毀工作時(shí),就會調(diào)用跟它關(guān)聯(lián)在一起的PoolableObjectFactory實(shí)例的相應(yīng)方法來操作。
PoolableObjectFactory是在org.apache.commons.pool包中定義的一個接口。實(shí)際使用的時(shí)候需要利用這個接口的一個具體實(shí)現(xiàn)。Pool組件本身沒有包含任何一種PoolableObjectFactory實(shí)現(xiàn),需要根據(jù)情況自行創(chuàng)立。
創(chuàng)立PoolableObjectFactory的大體步驟是:
創(chuàng)建一個實(shí)現(xiàn)了PoolableObjectFactory接口的類。
import org.apache.commons.pool.PoolableObjectFactory;
public class PoolableObjectFactorySample
implements PoolableObjectFactory {
private static int counter = 0;
}
為這個類添加一個Object makeObject()方法。這個方法用于在必要時(shí)產(chǎn)生新的對象。
public Object makeObject() throws Exception {
Object obj = String.valueOf(counter++);
System.err.println("Making Object " + obj);
return obj;
}
為這個類添加一個void activateObject(Object obj)方法。這個方法用于將對象“激活”??設(shè)置為適合開始使用的狀態(tài)。
public void activateObject(Object obj) throws Exception {
System.err.println("Activating Object " + obj);
}
為這個類添加一個void passivateObject(Object obj)方法。這個方法用于將對象“掛起”??設(shè)置為適合開始休眠的狀態(tài)。
public void passivateObject(Object obj) throws Exception {
System.err.println("Passivating Object " + obj);
}
為這個類添加一個boolean validateObject(Object obj)方法。這個方法用于校驗(yàn)一個具體的對象是否仍然有效,已失效的對象會被自動交給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類似這個樣子:
PoolableObjectFactorySample.java
import org.apache.commons.pool.PoolableObjectFactory;
public class PoolableObjectFactorySample
implements PoolableObjectFactory {
private static int counter = 0;
public Object makeObject() throws Exception {
Object obj = String.valueOf(counter++);
System.err.println("Making Object " + obj);
return obj;
}
public void activateObject(Object obj) throws Exception {
System.err.println("Activating Object " + obj);
}
public void passivateObject(Object obj) throws Exception {
System.err.println("Passivating Object " + obj);
}
public boolean validateObject(Object obj) {
/* 以1/2的概率將對象判定為失效 */
boolean result = (Math.random() > 0.5);
System.err.println("Validating Object "
+ obj + " : " + result);
return result;
}
public void destroyObject(Object obj) throws Exception {
System.err.println("Destroying Object " + obj);
}
}
使用ObjectPool
有了合適的PoolableObjectFactory之后,便可以開始請出ObjectPool來與之同臺演出了。
ObjectPool是在org.apache.commons.pool包中定義的一個接口,實(shí)際使用的時(shí)候也需要利用這個接口的一個具體實(shí)現(xiàn)。Pool組件本身包含了若干種現(xiàn)成的ObjectPool實(shí)現(xiàn),可以直接利用。如果都不合用,也可以根據(jù)情況自行創(chuàng)建。具體的創(chuàng)建方法,可以參看Pool組件的文檔和源碼。
ObjectPool的使用方法類似這樣:
生成一個要用的PoolableObjectFactory類的實(shí)例。
PoolableObjectFactory factory = new PoolableObjectFactorySample();
利用這個PoolableObjectFactory實(shí)例為參數(shù),生成一個實(shí)現(xiàn)了ObjectPool接口的類(例如StackObjectPool)的實(shí)例,作為對象池。
ObjectPool pool = new StackObjectPool(factory);
需要從對象池中取出對象時(shí),調(diào)用該對象池的Object borrowObject()方法。
Object obj = null;
obj = pool.borrowObject();
需要將對象放回對象池中時(shí),調(diào)用該對象池的void returnObject(Object obj)方法。
pool.returnObject(obj);
當(dāng)不再需要使用一個對象池時(shí),調(diào)用該對象池的void close()方法,釋放它所占據(jù)的資源。
pool.close();
這些操作都可能會拋出異常,需要另外處理。
比較完整的使用ObjectPool的全過程,可以參考這段代碼:
ObjectPoolSample.java
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPool;
public class ObjectPoolSample {
public static void main(String[] args) {
Object obj = null;
PoolableObjectFactory factory
= new PoolableObjectFactorySample();
ObjectPool pool = new StackObjectPool(factory);
try {
for(long i = 0; i < 100 ; i++) {
System.out.println("== " + i + " ==");
obj = pool.borrowObject();
System.out.println(obj);
pool.returnObject(obj);
}
obj = null;//明確地設(shè)為null,作為對象已歸還的標(biāo)志
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
if (obj != null) {//避免將一個對象歸還兩次
pool.returnObject(obj);
}
pool.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
另外,ObjectPool接口還定義了幾個可以由具體的實(shí)現(xiàn)決定要不要支持的操作,包括:
void clear()
清除所有當(dāng)前在此對象池中休眠的對象。
int getNumActive()
返回已經(jīng)從此對象池中借出的對象的總數(shù)。
int getNumIdle()
返回當(dāng)前在此對象池中休眠的對象的數(shù)目。
void setFactory(PoolableObjectFactory factory)
將當(dāng)前對象池與參數(shù)中給定的PoolableObjectFactory相關(guān)聯(lián)。如果在當(dāng)前狀態(tài)下,無法完成這一操作,會有一個IllegalStateException異常拋出。
如果所用的ObjectPool實(shí)現(xiàn)不支持這些操作,那么調(diào)用這些方法的時(shí)候,會拋出一個UnsupportedOperationException異常。
利用ObjectPoolFactory
有時(shí)候,要在多處生成類型和設(shè)置都相同的ObjectPool。如果在每個地方都重寫一次調(diào)用相應(yīng)構(gòu)造方法的代碼,不但比較麻煩,而且日后修改起來,也有所不便。這種時(shí)候,正是使用ObjectPoolFactory的時(shí)機(jī)。
ObjectPoolFactory是一個在org.apache.commons.pool中定義的接口,它定義了一個稱為ObjectPool createPool()方法,可以用于大量生產(chǎn)類型和設(shè)置都相同的ObjectPool。
Pool組件中,對每一個ObjectPool實(shí)現(xiàn),都有一個對應(yīng)的ObjectPoolFactory實(shí)現(xiàn)。它們相互之間,有一一對應(yīng)的參數(shù)相同的構(gòu)造方法。使用的時(shí)候,只要先用想要的參數(shù)和想用的ObjectPoolFactory實(shí)例,構(gòu)造出一個ObjectPoolFactory對象,然后在需要生成ObjectPool的地方,調(diào)用這個對象的createPool()方法就可以了。日后無論想要調(diào)整所用ObjectPool的參數(shù)還是類型,只需要修改這一處,就可以大功告成了。
將《使用ObjectPool》一節(jié)中的例子,改為使用ObjectPoolFactory來生成所用的ObjectPool對象之后,基本就是這種形式:
ObjectPoolFactorySample.java
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.ObjectPoolFactory;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPoolFactory;
public class ObjectPoolFactorySample {
public static void main(String[] args) {
Object obj = null;
PoolableObjectFactory factory
= new PoolableObjectFactorySample();
ObjectPoolFactory poolFactory
= new StackObjectPoolFactory(factory);
ObjectPool pool = poolFactory.createPool();
try {
for(long i = 0; i < 100 ; i++) {
System.out.println("== " + i + " ==");
obj = pool.borrowObject();
System.out.println(obj);
pool.returnObject(obj);
}
obj = null;
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
if (obj != null) {
pool.returnObject(obj);
}
pool.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
借助BasePoolableObjectFactory
PoolableObjectFactory定義了許多方法,可以適應(yīng)多種不同的情況。但是,在并沒有什么特殊需要的時(shí)候,直接實(shí)現(xiàn)PoolableObjectFactory接口,就要編寫若干的不進(jìn)行任何操作,或是始終返回true的方法來讓編譯通過,比較繁瑣。這種時(shí)候就可以借助BasePoolableObjectFactory的威力,來簡化編碼的工作。
BasePoolableObjectFactory是org.apache.commons.pool包中的一個抽象類。它實(shí)現(xiàn)了PoolableObjectFactory接口,并且為除了makeObject之外的方法提供了一個基本的實(shí)現(xiàn)??activateObject、passivateObject和destroyObject不進(jìn)行任何操作,而validateObject始終返回true。通過繼承這個類,而不是直接實(shí)現(xiàn)PoolableObjectFactory接口,就可以免去編寫一些只起到讓編譯通過的作用的代碼的麻煩了。
這個例子展示了一個從BasePoolableObjectFactory擴(kuò)展而來的PoolableObjectFactory:
BasePoolableObjectFactorySample.java
import org.apache.commons.pool.BasePoolableObjectFactory;
public class BasePoolableObjectFactorySample
extends BasePoolableObjectFactory {
private int counter = 0;
public Object makeObject() throws Exception {
return String.valueOf(counter++);
}
}
各式各樣的ObjectPool
可口可樂公司的軟飲料有可口可樂、雪碧和芬達(dá)等品種,百事可樂公司的軟飲料有百事可樂、七喜和美年達(dá)等類型,而Pool組件提供的ObjectPool實(shí)現(xiàn)則有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等種類。
不同類型的軟飲料各有各自的特點(diǎn),分別適應(yīng)不同消費(fèi)者的口味;而不同類型的ObjectPool也各有各自的特色,分別適應(yīng)不同的情況。
StackObjectPool
StackObjectPool利用一個java.util.Stack對象來保存對象池里的對象。這種對象池的特色是:
可以為對象池指定一個初始的參考大?。ó?dāng)空間不夠時(shí)會自動增長)。
在對象池已空的時(shí)候,調(diào)用它的borrowObject方法,會自動返回新創(chuàng)建的實(shí)例。
可以為對象池指定一個可保存的對象數(shù)目的上限。達(dá)到這個上限之后,再向池里送回的對象會被自動送去回收。
StackObjectPool的構(gòu)造方法共有六個,其中:
最簡單的一個是StackObjectPool(),一切采用默認(rèn)的設(shè)置,也不指明要用的PoolableObjectFactory實(shí)例。
最復(fù)雜的一個則是StackObjectPool(PoolableObjectFactory factory, int max, int init)。其中:
參數(shù)factory指明要與之配合使用的PoolableObjectFactory實(shí)例;
參數(shù)max設(shè)定可保存對象數(shù)目的上限;
參數(shù)init則指明初始的參考大小。
剩余的四個構(gòu)造方法則是最復(fù)雜的構(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實(shí)例,必須要在用它的setFactory(PoolableObjectFactory factory)方法與某一PoolableObjectFactory實(shí)例關(guān)聯(lián)起來后才能正常使用。
這種對象池可以在沒有Jakarta Commmons Collections組件支持的情況下正常運(yùn)行。
SoftReferenceObjectPool
SoftReferenceObjectPool利用一個java.util.ArrayList對象來保存對象池里的對象。不過它并不在對象池里直接保存對象本身,而是保存它們的“軟引用”(Soft Reference)。這種對象池的特色是:
可以保存任意多個對象,不會有容量已滿的情況發(fā)生。
在對象池已空的時(shí)候,調(diào)用它的borrowObject方法,會自動返回新創(chuàng)建的實(shí)例。
可以在初始化同時(shí),在池內(nèi)預(yù)先創(chuàng)建一定量的對象。
當(dāng)內(nèi)存不足的時(shí)候,池中的對象可以被Java虛擬機(jī)回收。
SoftReferenceObjectPool的構(gòu)造方法共有三個,其中:
最簡單的是SoftReferenceObjectPool(),不預(yù)先在池內(nèi)創(chuàng)建對象,也不指明要用的PoolableObjectFactory實(shí)例。
最復(fù)雜的一個則是SoftReferenceObjectPool(PoolableObjectFactory factory, int initSize)。其中:
參數(shù)factory指明要與之配合使用的PoolableObjectFactory實(shí)例
參數(shù)initSize則指明初始化時(shí)在池中創(chuàng)建多少個對象。
剩下的一個構(gòu)造方法,則是最復(fù)雜的構(gòu)造方法在某方面的簡化版本,適合在大多數(shù)情況下使用。它是:
SoftReferenceObjectPool(PoolableObjectFactory factory)
用不帶factory參數(shù)的構(gòu)造方法構(gòu)造的SoftReferenceObjectPool實(shí)例,也要在用它的setFactory(PoolableObjectFactory factory)方法與某一PoolableObjectFactory實(shí)例關(guān)聯(lián)起來后才能正常使用。
這種對象池也可以在沒有Jakarta Commmons Collections組件支持的情況下正常運(yùn)行。
GenericObjectPool
GenericObjectPool利用一個org.apache.commons.collections.CursorableLinkedList對象來保存對象池里的對象。這種對象池的特色是:
可以設(shè)定最多能從池中借出多少個對象。
可以設(shè)定池中最多能保存多少個對象。
可以設(shè)定在池中已無對象可借的情況下,調(diào)用它的borrowObject方法時(shí)的行為,是等待、創(chuàng)建新的實(shí)例還是拋出異常。
可以分別設(shè)定對象借出和還回時(shí),是否進(jìn)行有效性檢查。
可以設(shè)定是否使用一個單獨(dú)的線程,對池內(nèi)對象進(jìn)行后臺清理。
GenericObjectPool的構(gòu)造方法共有七個,其中:
最簡單的一個是GenericObjectPool(PoolableObjectFactory factory)。僅僅指明要用的PoolableObjectFactory實(shí)例,其它參數(shù)則采用默認(rèn)值。
最復(fù)雜的一個是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í)例。
參數(shù)maxActive指明能從池中借出的對象的最大數(shù)目。如果這個值不是正數(shù),表示沒有限制。
參數(shù)whenExhaustedAction指定在池中借出對象的數(shù)目已達(dá)極限的情況下,調(diào)用它的borrowObject方法時(shí)的行為??梢赃x用的值有:
GenericObjectPool.WHEN_EXHAUSTED_BLOCK,表示等待;
GenericObjectPool.WHEN_EXHAUSTED_GROW,表示創(chuàng)建新的實(shí)例(不過這就使maxActive參數(shù)失去了意義);
GenericObjectPool.WHEN_EXHAUSTED_FAIL,表示拋出一個java.util.NoSuchElementException異常。
參數(shù)maxWait指明若在對象池空時(shí)調(diào)用borrowObject方法的行為被設(shè)定成等待,最多等待多少毫秒。如果等待時(shí)間超過了這個數(shù)值,則會拋出一個java.util.NoSuchElementException異常。如果這個值不是正數(shù),表示無限期等待。
參數(shù)testOnBorrow設(shè)定在借出對象時(shí)是否進(jìn)行有效性檢查。
參數(shù)testOnBorrow設(shè)定在還回對象時(shí)是否進(jìn)行有效性檢查。
參數(shù)timeBetweenEvictionRunsMillis,設(shè)定間隔每過多少毫秒進(jìn)行一次后臺對象清理的行動。如果這個值不是正數(shù),則實(shí)際上不會進(jìn)行后臺對象清理。
參數(shù)numTestsPerEvictionRun,設(shè)定在進(jìn)行后臺對象清理時(shí),每次檢查幾個對象。如果這個值不是正數(shù),則每次檢查的對象數(shù)是檢查時(shí)池內(nèi)對象的總數(shù)乘以這個值的負(fù)倒數(shù)再向上取整的結(jié)果??也就是說,如果這個值是-2(-3、-4、-5……)的話,那么每次大約檢查當(dāng)時(shí)池內(nèi)對象總數(shù)的1/2(1/3、1/4、1/5……)左右。
參數(shù)minEvictableIdleTimeMillis,設(shè)定在進(jìn)行后臺對象清理時(shí),視休眠時(shí)間超過了多少毫秒的對象為過期。過期的對象將被回收。如果這個值不是正數(shù),那么對休眠時(shí)間沒有特別的約束。
參數(shù)testWhileIdle,則設(shè)定在進(jìn)行后臺對象清理時(shí),是否還對沒有過期的池內(nèi)對象進(jìn)行有效性檢查。不能通過有效性檢查的對象也將被回收。
另一個比較特別的構(gòu)造方法是GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 。其中:
參數(shù)factory指明要與之配合使用的PoolableObjectFactory實(shí)例;
參數(shù)config則指明一個包括了各個參數(shù)的預(yù)設(shè)值的對象(詳見《GenericObjectPool.Config》一節(jié))。
剩下的五個構(gòu)造函數(shù)則是最復(fù)雜的構(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組件支持的情況下運(yùn)行。
GenericObjectPool.Config
調(diào)用一個有很多的參數(shù)的方法的時(shí)候,很可能將參數(shù)的位置和個數(shù)搞錯,導(dǎo)致編譯或運(yùn)行時(shí)的錯誤;閱讀包含了有很多參數(shù)的方法調(diào)用的代碼的時(shí)候,也很可能因?yàn)闆]有搞對參數(shù)的位置和個數(shù),產(chǎn)生錯誤的理解。因此,人們往往避免給一個方法安排太多的參數(shù)的做法(所謂的“Long Parameter List”)。不過,有些方法又確實(shí)需要許多參數(shù)才能完成工作。于是,就有人想到了一種將大批的參數(shù)封裝到一個對象(稱為參數(shù)對象,Parameter Object)里,然后將這個對象作為單一的參數(shù)傳遞的兩全其美的對策。
因?yàn)樯蒅enericKeyedObjectPool時(shí)可供設(shè)置的特性非常之多,所以它的構(gòu)造方法里也就難免會需要不少的參數(shù)。GenericKeyedObjectPool除了提供了幾個超長的構(gòu)造方法之外,同時(shí)也定義了一個使用參數(shù)對象的構(gòu)造方法。所用參數(shù)對象的類型是GenericKeyedObjectPool.Config。
GenericKeyedObjectPool.Config定義了許多的public字段,每個對應(yīng)一種可以為GenericKeyedObjectPool設(shè)置的特性,包括:
int maxActive
int maxIdle
long maxWait
long minEvictableIdleTimeMillis
int numTestsPerEvictionRun
boolean testOnBorrow
boolean testOnReturn
boolean testWhileIdle
long timeBetweenEvictionRunsMillis
byte whenExhaustedAction
這些字段的作用,與在GenericKeyedObjectPool最復(fù)雜的構(gòu)造方法中與它們同名的參數(shù)完全相同。
使用的時(shí)候,先生成一個GenericKeyedObjectPool.Config對象,然后將個字段設(shè)置為想要的值,最后用這個對象作為唯一的參數(shù)調(diào)用GenericKeyedObjectPool的構(gòu)造方法即可。
注意:使用有許多public字段、卻沒有任何方法的類,也是一個人們往往加以避免的行為(所謂的“Data Class”)。不過這次GenericKeyedObjectPool特立獨(dú)行了一回。
帶鍵值的對象池
有時(shí)候,單用對池內(nèi)所有對象一視同仁的對象池,并不能解決的問題。例如,對于一組某些參數(shù)設(shè)置不同的同類對象??比如一堆指向不同地址的java.net.URL對象或者一批代表不同語句的java.sql.PreparedStatement對象,用這樣的方法池化,就有可能取出不合用的對象的麻煩。
可以通過為每一組參數(shù)相同的同類對象建立一個單獨(dú)的對象池來解決這個問題。但是,如果使用普通的ObjectPool來實(shí)施這個計(jì)策的話,因?yàn)槠胀ǖ腜oolableObjectFactory只能生產(chǎn)出大批設(shè)置完全一致的對象,就需要為每一組參數(shù)相同的對象編寫一個單獨(dú)的PoolableObjectFactory,工作量相當(dāng)可觀。這種時(shí)候就適合調(diào)遣Pool組件中提供的一種“帶鍵值的對象池”來展開工作了。
Pool組件采用實(shí)現(xiàn)了KeyedObjectPool接口的類,來充當(dāng)帶鍵值的對象池。相應(yīng)的,這種對象池需要配合實(shí)現(xiàn)了KeyedPoolableObjectFactory接口的類和實(shí)現(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,用于充當(dāng)和BasePoolableObjectFactory差不多的角色。
KeyedObjectPool和ObjectPool的形式大同小異,只是某些方法的參數(shù)類型發(fā)生了變化,某些方法分成了兩種略有不同的版本:
用Object borrowObject(Object key)和void returnObject(Object key, Object obj)來負(fù)責(zé)對象出借和歸還的動作。
用void close()來關(guān)閉不再需要的對象池。
用void clear(Object key)和void clear()來清空池中的對象,前者針對與特定鍵值相關(guān)聯(lián)的實(shí)例,后者針對整個對象池。
用int getNumActive(Object key)和int getNumActive()來查詢已借出的對象數(shù),前者針對與特定鍵值相關(guān)聯(lián)的實(shí)例,后者針對整個對象池。
用int getNumIdle(Object key)和int getNumIdle()來查詢正在休眠的對象數(shù),前者針對與特定鍵值相關(guān)聯(lián)的實(shí)例,后者針對整個對象池。
用void setFactory(KeyedPoolableObjectFactory factory)來設(shè)置要用的KeyedPoolableObjectFactory實(shí)例。
void clear、int getNumActive、int getNumIdle和void setFactory的各種版本都仍然是可以由具體實(shí)現(xiàn)自行決定是否要支持的方法。如果所用的KeyedObjectPool實(shí)現(xiàn)不支持這些操作,那么調(diào)用這些方法的時(shí)候,會拋出一個UnsupportedOperationException異常。
KeyedObjectPoolFactory和ObjectPoolFactory的形式完全相同,只是所代表的對象不同而已。
這一類對象池的基本使用方法接近于這樣:
KeyedObjectPoolSample.java
import org.apache.commons.pool.BaseKeyedPoolableObjectFactory;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;
class KeyedPoolableObjectFactorySample
extends BaseKeyedPoolableObjectFactory {
public Object makeObject(Object key) throws Exception {
return new String("[" + key.hashCode() + "]");
}
}
public class KeyedObjectPoolSample {
public static void main(String[] args) {
Object obj = null;
KeyedPoolableObjectFactory factory
= new KeyedPoolableObjectFactorySample();
KeyedObjectPoolFactory poolFactory
= new StackKeyedObjectPoolFactory(factory);
KeyedObjectPool pool = poolFactory.createPool();
String key = null;
try {
for (long i = 0; i < 100 ; i++) {
key = "" + (int) (Math.random() * 10);
System.out.println("== " + i + " ==");
System.out.println("Key:" + key);
obj = pool.borrowObject(key);
System.out.println("Object:" + obj);
pool.returnObject(key, obj);
obj = null;
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
if (obj != null) {
pool.returnObject(key, obj);
}
pool.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
Pool組件自帶的KeyedObjectPool的實(shí)現(xiàn)有StackKeyedObjectPool和GenericKeyedObjectPool兩種。它們的使用方法分別與它們各自的近親KeyedObjectPool和KeyedObjectPool基本一致,只是原來使用GenericObjectPool.Config的地方要使用GenericKeyedObjectPool.Config代替。
當(dāng)出借少于歸還
Java并未提供一種機(jī)制來保證兩個方法被調(diào)用的次數(shù)之間呈現(xiàn)一種特定的關(guān)系(相等,相差一個常數(shù),或是其它任何關(guān)系)。因此,完全可以做到建立一個ObjectPool對象,然后調(diào)用一次borrowObject方法,借出一個對象,之后重復(fù)兩次returnObject方法調(diào)用,進(jìn)行兩次歸還。而調(diào)用一個從不曾借出對象的ObjectPool的returnObject方法也并不是一個不可完成的任務(wù)。
盡管這些使用方法并不合乎returnObject的字面意思,但是Pool組件中的各個ObjectPool/KeyedObjectPool實(shí)現(xiàn)都不在乎這一點(diǎn)。它們的returnObject方法都只是單純地召喚與當(dāng)前對象池關(guān)聯(lián)的PoolableObjectFactory實(shí)例,看這對象能否經(jīng)受得起validateObject的考驗(yàn)而已??简?yàn)的結(jié)果決定了這個對象是應(yīng)該拿去作passivateObject處理,而后留待重用;還是應(yīng)該拿去作destroyObject處理,以免占用資源。也就是說,當(dāng)出借少于歸還的時(shí)候,并不會額外發(fā)生什么特別的事情(當(dāng)然,有可能因?yàn)樵搶ο蟪靥幱诓唤邮軞w還對象的請求的狀態(tài)而拋出異常,不過這是常規(guī)現(xiàn)象)。
在實(shí)際使用中,可以利用這一特性來向?qū)ο蟪貎?nèi)加入通過其它方法生成的對象。
線程安全問題
有時(shí)候可能要在多線程環(huán)境下使用Pool組件,這時(shí)候就會遇到和Pool組件的線程安全程度有關(guān)的問題。
因?yàn)镺bjectPool和KeyedObjectPool都是在org.apache.commons.pool中定義的接口,而在接口中無法使用“synchronized”來修飾方法,所以,一個ObjectPool/KeyedObjectPool下的各個方法是否是同步方法,完全要看具體的實(shí)現(xiàn)。而且,單純地使用了同步方法,也并不能使對象就此在多線程環(huán)境里高枕無憂。
就Pool組件中自帶的幾個ObjectPool/KeyedObjectPool的實(shí)現(xiàn)而言,它們都在一定程度上考慮了在多線程環(huán)境中使用的情況。不過還不能說它們是完全“線程安全”的。
例如,這段代碼有些時(shí)候就會有一些奇怪的表現(xiàn),最后輸出的結(jié)果比預(yù)期的要大:
UnsafeMultiThreadPoolingSample.java
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;
class UnsafePicker extends Thread {
private ObjectPool pool;
public UnsafePicker(ObjectPool op) {
pool = op;
}
public void run() {
Object obj = null;
try {
/* 似乎…… */
if ( pool.getNumActive() < 5 ) {
sleep((long) (Math.random() * 10));
obj = pool.borrowObject();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
public class UnsafeMultiThreadPoolingSample {
public static void main(String[] args) {
ObjectPool pool = new StackObjectPool
(new BasePoolableObjectFactorySample());
Thread ts[] = new Thread[20];
for (int j = 0; j < ts.length; j++) {
ts[j] = new UnsafePicker(pool);
ts[j].start();
}
try {
Thread.sleep(1000);
/* 然而…… */
System.out.println("NumActive:" + pool.getNumActive());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
要避免這種情況,就要進(jìn)一步采取一些措施才行:
SafeMultiThreadPoolingSample.java
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;
class SafePicker extends Thread {
private ObjectPool pool;
public SafePicker(ObjectPool op) {
pool = op;
}
public void run() {
Object obj = null;
try {
/* 略加處理 */
synchronized (pool) {
if ( pool.getNumActive() < 5 ) {
sleep((long) (Math.random() * 10));
obj = pool.borrowObject();
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
public class SafeMultiThreadPoolingSample {
public static void main(String[] args) {
ObjectPool pool = new StackObjectPool
(new BasePoolableObjectFactorySample());
Thread ts[] = new Thread[20];
for (int j = 0; j < ts.length; j++) {
ts[j] = new SafePicker(pool);
ts[j].start();
}
try {
Thread.sleep(1000);
System.out.println("NumActive:" + pool.getNumActive());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
基本上,可以說Pool組件是線程相容的。但是要在多線程環(huán)境中使用,還需要作一些特別的處理。
什么時(shí)候不要池化
采用對象池化的本意,是要通過減少對象生成的次數(shù),減少花在對象初始化上面的開銷,從而提高整體的性能。然而池化處理本身也要付出代價(jià),因此,并非任何情況下都適合采用對象池化。
Dr. Cliff Click在JavaOne 2003上發(fā)表的《Performance Myths Exposed》中,給出了一組其它條件都相同時(shí),使用與不使用對象池化技術(shù)的實(shí)際性能的比較結(jié)果。他的實(shí)測結(jié)果表明:
對于類似Point這樣的輕量級對象,進(jìn)行池化處理后,性能反而下降,因此不宜池化;
對于類似Hashtable這樣的中量級對象,進(jìn)行池化處理后,性能基本不變,一般不必池化(池化會使代碼變復(fù)雜,增大維護(hù)的難度);
對于類似JPanel這樣的重量級對象,進(jìn)行池化處理后,性能有所上升,可以考慮池化。
根據(jù)使用方法的不同,實(shí)際的情況可能與這一測量結(jié)果略有出入。在配置較高的機(jī)器和技術(shù)較強(qiáng)的虛擬機(jī)上,不宜池化的對象的范圍可能會更大。不過,對于像網(wǎng)絡(luò)和數(shù)據(jù)庫連接這類重量級的對象來說,目前還是有池化的必要。
基本上,只在重復(fù)生成某種對象的操作成為影響性能的關(guān)鍵因素的時(shí)候,才適合進(jìn)行對象池化。如果進(jìn)行池化所能帶來的性能提高并不重要的話,還是不采用對象池化技術(shù),以保持代碼的簡明,而使用更好的硬件和更棒的虛擬機(jī)來提高性能為佳。
結(jié)束語
恰當(dāng)?shù)厥褂脤ο蟪鼗梢杂行У亟档皖l繁生成某些對象所造成的開銷,從而提高整體的性能。而借助Jakarta Commons Pool組件,可以有效地減少花在處理對象池化上的工作量,進(jìn)而,向其它重要的工作里,投入更多的時(shí)間和精力。
參考資料
很多疑難問題的答案都可以通過查閱Pool組件的Javadoc文檔和源代碼的方法解決。
從 Pool組件的官方站點(diǎn) 上,還可以進(jìn)一步得到許多有用的信息。
DBCP 是一個基于Pool組件的Java數(shù)據(jù)庫連接池管理組件,同時(shí)也可以作為Pool組件的用法示例使用。
蔡學(xué)鏞在 《Java夜未眠(Sleepless Java)》 中的 《測不準(zhǔn)原理》一文里,介紹了Java中的包括“軟引用”(Soft Reference)在內(nèi)的各種不同的引用的特點(diǎn)和用處。
Martin Fowler在 《Refactoring -- Improving the Design of Existing Code》(中譯本名為《重構(gòu)??改善既有代碼的設(shè)計(jì)》,由侯捷、熊節(jié)合譯)一書的第三章《代碼的壞味道(Bad Smells in Code)》中討論了被稱為“Long Parameter List”和“Data Class”的“壞味道”。并指明了一些可以用于對付這些問題的重構(gòu)手法。
Brian Goetz在IBM developerWorks上發(fā)表的《Java 理論與實(shí)踐:描繪線程安全性》一文中,說明了為什么單純地使用同步方法還不能讓對象就此在多線程環(huán)境里高枕無憂的原因。
Dr. Cliff Click發(fā)表在JavaOne 2003上的《Performance Myths Exposed》(Session #1522),給出了一組包括“對象池化”在內(nèi)的、對“能提高Java程序性能”的做法的實(shí)際效果的測試數(shù)據(jù),和一些恰當(dāng)使用這些做法的建議。
關(guān)于作者
孫海濤從1994年6月的一個風(fēng)雨交加的下午開始了他的編程生涯。目前,他的興趣集中于Java、Web、開源軟件和人機(jī)交互。但是,這并不表示他不會對其它的事物給予足夠的關(guān)心和重視??梢酝ㄟ^ alexhsun@hotmail.com 與他取得聯(lián)系。
原文:
http://www.jspcn.net/htmlnews/11049384467341731.html