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