Vincent

          Vicent's blog
          隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
          數(shù)據(jù)加載中……

          使用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)“維護對象池的開銷”大于“生成新對象的開銷”,從而使性能降低的情況。但是對于生成時開銷可觀的對象,池化技術就是提高性能的有效策略了。

          When to use sidebars

          說明:英語中的Pool除了“池”之外,還有“供多方共享的資源”意思。作者十分懷疑第二種才是“Object Pool”中的Pool的實際含義,但是“對象池”的說法已經(jīng)廣為流傳,而一時又沒有足以替代的貼切譯法,因此這里仍然沿用這種譯名。





          回頁首


          Jakarta Commons Pool組件

          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外,還需要先準備下列一些東西:

          以上兩種軟件均有已編譯包和源代碼包兩種形式可供選擇。一般情況下,使用已編譯包即可。不過建議同時也下載源代碼包,作為參考資料使用。

          如果打算使用源代碼包自行編譯,那么還需要準備以下一些東西:

          具體的編譯方法,可以參看有關的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的大體步驟是:

          1. 創(chuàng)建一個實現(xiàn)了PoolableObjectFactory接口的類。

            import org.apache.commons.pool.PoolableObjectFactory;
            
            public class PoolableObjectFactorySample
                    implements PoolableObjectFactory {
                private static int counter = 0;
            }
            

          2. 為這個類添加一個Object makeObject()方法。這個方法用于在必要時產(chǎn)生新的對象。

            public Object makeObject() throws Exception {
                Object obj = String.valueOf(counter++);
                System.err.println("Making Object " + obj);
                return obj;
            }
            
            

          3. 為這個類添加一個void activateObject(Object obj)方法。這個方法用于將對象“激活”――設置為適合開始使用的狀態(tài)。

            public void activateObject(Object obj) throws Exception {
                System.err.println("Activating Object " + obj);
            }
            
            

          4. 為這個類添加一個void passivateObject(Object obj)方法。這個方法用于將對象“掛起”――設置為適合開始休眠的狀態(tài)。

            public void passivateObject(Object obj) throws Exception {
                System.err.println("Passivating Object " + obj);
            }
            
            

          5. 為這個類添加一個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;
            }
            

          6. 為這個類添加一個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包中定義的一個接口,實際使用的時候也需要利用這個接口的一個具體實現(xiàn)。Pool組件本身包含了若干種現(xiàn)成的ObjectPool實現(xiàn),可以直接利用。如果都不合用,也可以根據(jù)情況自行創(chuàng)建。具體的創(chuàng)建方法,可以參看Pool組件的文檔和源碼。

          ObjectPool的使用方法類似這樣:

          1. 生成一個要用的PoolableObjectFactory類的實例。

            PoolableObjectFactory factory = new PoolableObjectFactorySample();
            

          2. 利用這個PoolableObjectFactory實例為參數(shù),生成一個實現(xiàn)了ObjectPool接口的類(例如StackObjectPool)的實例,作為對象池。

            ObjectPool pool = new StackObjectPool(factory);
            

          3. 需要從對象池中取出對象時,調(diào)用該對象池的Object borrowObject()方法。

            Object obj = null;
            obj = pool.borrowObject();
            

          4. 需要將對象放回對象池中時,調(diào)用該對象池的void returnObject(Object obj)方法。

            pool.returnObject(obj);
            

          5. 當不再需要使用一個對象池時,調(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;//明確地設為null,作為對象已歸還的標志
                  }
                  catch (Exception e) {
                      e.printStackTrace();
                  }
                  finally {
                      try{
                          if (obj != null) {//避免將一個對象歸還兩次
                              pool.returnObject(obj);
                          }
                          pool.close();
                      }
                      catch (Exception e){
                          e.printStackTrace();
                      }
                  }
              }
          }
          
          												
          										

          另外,ObjectPool接口還定義了幾個可以由具體的實現(xiàn)決定要不要支持的操作,包括:

          void clear()

          清除所有當前在此對象池中休眠的對象。

          int getNumActive()

          返回已經(jīng)從此對象池中借出的對象的總數(shù)。

          int getNumIdle()

          返回當前在此對象池中休眠的對象的數(shù)目。

          void setFactory(PoolableObjectFactory factory)

          將當前對象池與參數(shù)中給定的PoolableObjectFactory相關聯(lián)。如果在當前狀態(tài)下,無法完成這一操作,會有一個IllegalStateException異常拋出。





          回頁首


          利用ObjectPoolFactory

          有時候,要在多處生成類型和設置都相同的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對象之后,基本就是這種形式:

          												
          														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定義了許多方法,可以適應多種不同的情況。但是,在并沒有什么特殊需要的時候,直接實現(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:

          												
          														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實現(xiàn)則有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等種類。

          不同類型的軟飲料各有各自的特點,分別適應不同消費者的口味;而不同類型的ObjectPool也各有各自的特色,分別適應不同的情況。





          回頁首


          StackObjectPool

          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

          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

          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組件支持的情況下運行。





          回頁首


          GenericObjectPool.Config

          調(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的形式完全相同,只是所代表的對象不同而已。

          這一類對象池的基本使用方法接近于這樣:

          												
          														
          																
          																		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的實現(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é)果比預期的要大:

          												
          														
          																
          																		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組件是線程相容的。但是要在多線程環(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

          主站蜘蛛池模板: 田阳县| 菏泽市| 新竹县| 凤城市| 平顶山市| 桑植县| 大石桥市| 灵山县| 安远县| 永川市| 北辰区| 二连浩特市| 如皋市| 乌拉特后旗| 武隆县| 陇西县| 潞城市| 镇沅| 鸡泽县| 永宁县| 普洱| 镇雄县| 长岛县| 西丰县| 澄城县| 习水县| 阿城市| 洛川县| 安溪县| 黔东| 鄂州市| 泾阳县| 伊金霍洛旗| 玉龙| 桐城市| 湘潭县| 宜川县| 房产| 怀宁县| 漳州市| 庆云县|