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