读书人

Jakarta Commons Pool 对象池 施用测

发布时间: 2012-07-19 16:02:20 作者: rapoo

Jakarta Commons Pool 对象池 使用,测试

恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率。Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以有效地减少处理对象池化时的工作量,为其它重要的工作留下更多的精力和时间。

创建新的对象并初始化的操作,可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作(例如,从一台位于20,000千米以外的主机上读出一些数据)的时候,尤其是这样。在需要大量生成这样的对象的时候,就可能会对性能造成一些不可忽略的影响。要缓解这个问题,除了选用更好的硬件和更棒的虚拟机以外,适当地采用一些能够减少对象创建次数的编码技巧,也是一种有效的对策。对象池化技术(Object Pooling)就是这方面的著名技巧,而JakartaCommons Pool组件则是处理对象池化的得力外援。?对象池化技术?对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)。?PoolableObjectFactory、ObjectPool和ObjectPoolFactory在Pool组件中,对象池化的工作被划分给了三类对象:PoolableObjectFactory用于管理被池化的对象的产生、激活、挂起、校验和销毁;?ObjectPool用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作;ObjectPoolFactory则用于大量生成相同类型和设置的ObjectPool。相应地,使用Pool组件的过程,也大体可以划分成“创立PoolableObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三种动作。?各式各样的ObjectPool?可口可乐公司的软饮料有可口可乐、雪碧和芬达等品种,百事可乐公司的软饮料有百事可乐、七喜和美年达等类型,而Pool组件提供的ObjectPool实现则有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等种类。不同类型的软饮料各有各自的特点,分别适应不同消费者的口味;而不同类型的ObjectPool也各有各自的特色,分别适应不同的情况。?StackObjectPoolStackObjectPool利用一个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组件支持的情况下正常运行。?SoftReferenceObjectPoolSoftReferenceObjectPool利用一个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组件支持的情况下正常运行。?GenericObjectPoolGenericObjectPool利用一个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 maxActiveint maxIdlelong maxWaitlong minEvictableIdleTimeMillisint numTestsPerEvictionRunboolean testOnBorrowboolean testOnReturnboolean testWhileIdlelong timeBetweenEvictionRunsMillisbyte 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的形式完全相同,只是所代表的对象不同而已。???下面我们进入编码部分:我们要做的就是利用object pool来存放数据库连接,因为数据库的连接的对象是个大对象,初始的时候需要与远程数据库交互,耗费时间和性能较大,我会在代码中详述,下面我们开始编码:???????如上所说,首先我们得新建一个facotry,PoolableObjectFactory是在org.apache.commons.pool包中定义的一个接口。实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现,需要根据情况自行创立。???????下面我们新建个factory,用来实施对对象的管理。
?输出日志为:makeObjectactivateObjectoracle.jdbc.driver.T4CConnection@84abc9validateObjectoracle.jdbc.driver.T4CConnection@84abc9=======0===oracle.jdbc.driver.T4CConnection@84abc9==========makeObjectactivateObjectoracle.jdbc.driver.T4CConnection@1050169validateObjectoracle.jdbc.driver.T4CConnection@1050169=======0===oracle.jdbc.driver.T4CConnection@1050169==========makeObjectactivateObjectoracle.jdbc.driver.T4CConnection@9fef6fvalidateObjectoracle.jdbc.driver.T4CConnection@9fef6f=======0===oracle.jdbc.driver.T4CConnection@9fef6f==========makeObject…………………………..//以下省略?从上面的日志我们看到,多线程并发的时候,这里也能动态的创建对象,而不会出现借不到的情况,可能又会有同志问,靠,你这是不是每个线程都会请求啊?其实呢,也不是,因为多线程并发的时候,创建的对象刚借出去还没还回来,另外的线程又过来借了。所以才会创建多个,下面我们再来做个测试,还是多线程,但是每个线程之间有个启动的时间差,这下我们看看会怎么样??以下为测试代码:
?下面我们来看日志:makeObjectactivateObjectoracle.jdbc.driver.T4CConnection@19bd03evalidateObjectoracle.jdbc.driver.T4CConnection@19bd03evalidateObjectoracle.jdbc.driver.T4CConnection@19bd03epassivateObjectoracle.jdbc.driver.T4CConnection@19bd03eactivateObjectoracle.jdbc.driver.T4CConnection@19bd03evalidateObjectoracle.jdbc.driver.T4CConnection@19bd03e=======0===oracle.jdbc.driver.T4CConnection@19bd03e==========validateObjectoracle.jdbc.driver.T4CConnection@19bd03epassivateObjectoracle.jdbc.driver.T4CConnection@19bd03eactivateObjectoracle.jdbc.driver.T4CConnection@19bd03evalidateObjectoracle.jdbc.driver.T4CConnection@19bd03e=======1===oracle.jdbc.driver.T4CConnection@19bd03e==========validateObjectoracle.jdbc.driver.T4CConnection@19bd03epassivateObjectoracle.jdbc.driver.T4CConnection@19bd03e……………………………以下省略N行?从上面的输出我们可以看到。如果及时归还的话,多个线程用的还是同一个对象,我们看连接后面的编号我们就知道啦。?如果你在调用过后不想用了,或者在程序关闭的时候要销毁对象,或者要重新生成新的对象,记得这样调用,呵呵。factory.destroyObject(obj);pool.clear();//这里如果不用pool.clear()的话,那对象里面还是存在的,不过都是null的连接,不能用的噢,会抛以上的噢。以上两行要放在一起。?总结:????通过以上的程序我们了解了这个对象池的好处,在我们自己做网站或者什么的小东西的时候,我们可以用这个,可以提高效率,在很多中间件里面已经有这个功能了,呵呵,这里主要是讲解,应用的价值还是有的,我们用tomcat跑程序的时候就可以用用这个。用weblogc,wps啥的,人家中间件里面的连接池比这个应该要NB很多,呵呵。以上是我的总结,也是对于看了这个对象池源代码过后总结的一个小的报告。 

读书人网 >开源软件

热点推荐