以下图片来源于http://commons.apache.org/pool/guide/sequencediagrams.html,加了点注释而已
所有的时间都是毫秒,并且 GenericObjectPool 是 thread safe,以下分别说明各个参数:
whenExhaustedAction
WHEN_EXHAUSTED_BLOCK
参考 maxWait
WHEN_EXHAUSTED_FAIL
WHEN_EXHAUSTED_GROW
maxWait
如果 whenExhaustedAction = WHEN_EXHAUSTED_BLOCK,那么 borrow 不能立即返回 Object时,
会wait,知道得到池化对象,或者超过 maxWait。如果maxWait < 1,那么 会一直等到获得池化对象
lifo
true/false
testOnBorrow
GenericObjectPool.borrowObject() 时 是否调用 PoolableObjectFactory.validateObject
testOnReturn
GenericObjectPool.returnObject() 时 是否调用 PoolableObjectFactory.validateObject
maxIdle 默认值为 DEFAULT_MAX_IDLE=8,超过该值的 idle 对象会被destory
基本上看到这里就能用了,最好别用 evictor,除非要求..... 例如 db长连接、心跳
timeBetweenEvictionRunsMillis
启动evictor thread的间隔,如果设置为<1不会启动evictor thread
evictor thread 按照规则下面定义来 填充/移除 池中的对象
evictor thread 在执行检查时会阻塞 borrow/return,因此如果该参数设置的很小,会造成evictor thread频繁执行而影响性能
Eviction runs require an exclusive synchronization
lock on the pool, so if they run too frequently and / or incur excessive
latency when creating, destroying or validating object instances,
performance issues may result
minIdle
如果evictor启动,evictor检查到池中的对象小于该值时,会创建min(minIdle, numActive + numIdle - maxActive) 的对象来填充池。小心使用该变量,设置为 <1最安全,可能导致dbcp死锁:dbcp-44,在common-pool 1.5 dbcp1.4中已经修正了这个问题,4年
numTestsPerEvictionRun
如果evictor启动,evictor每次启动时会检查池中的 x 个对象
numTestsPerEvictionRun > 0, x = min(_numTestsPerEvictionRun, GenericObjectPool.getNumIdle())
numTestsPerEvictionRun < 0, x = ceil( GenericObjectPool.getNumIdle())/abs(numTestsPerEvictionRun) )
i.e. abs(numTestsPerEvictionRun) 分之一的空闲对象会被检查
minEvictableIdleTimeMillis
如果evictor启动,evictor每次检查时可能会将池中空闲太久的对象移除池
如果设置为 < 1,是否把 idle 对象移除要看 softMinEvictableIdleTimeMillis
如果设置为 > 0,那么会把闲置了 minEvictableIdleTimeMillis 的对象PoolableObjectFactory.destroyObject掉
softMinEvictableIdleTimeMillis
如果evictor启动,并且minEvictableIdleTimeMillis < 1 或者按照 minEvictableIdleTimeMillis 计算对象不需要移出池
如果设置为 < 1,则不会移除任何idle对象
如果设置为 > 0,则当对象闲置的时间超过softMinEvictableIdleTimeMillis 且 GenericObjectPool.getNumIdle > minIdle 时 将对象移除池
一般应该把minEvictableIdleTimeMillis 配置为 -1,把softMinEvictableIdleTimeMillis配置为整数
_testWhileIdle
如果evictor启动,该变量为true则调用 PoolableObjectFactory.validateObject
备注1,minEvictableIdleTimeMillis 和 minEvictableIdleTimeMillis 的使用方法
if ((getMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getMinEvictableIdleTimeMillis())) {
removeObject = true;
} else if ((getSoftMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getSoftMinEvictableIdleTimeMillis()) &&
((getNumIdle() + 1)> getMinIdle())) { // +1 accounts for object we are processing
removeObject = true;
创建新的对象并初始化的操作,可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作(例如,从一台位于20,000千米以外的主机上读出一些数据)的时候,尤其是这样。在需要大量生成这样的对象的时候,就可能会对性能造成一些不可忽略的影响。要缓解这个问题,除了选用更好的硬件和更棒的虚拟机以外,适当地采用一些能够减少对象创建次数的编码技巧,也是一种有效的对策。对象池化技术(Object Pooling)就是这方面的著名技巧,而Jakarta Commons Pool组件则是处理对象池化的得力外援。
对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)。
对于没有状态的对象(例如String),在重复使用之前,无需进行任何处理;对于有状态的对象(例如StringBuffer),在重复使用之前,就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制,恢复某个对象的状态的操作不可能实现了的话,就得把这个对象抛弃,改用新创建的实例了。
并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。
Jakarta Commons Pool是一个用于在Java程序中实现对象池化的组件。它的基本情况是:
为了顺利的按照本文中提到的方法使用Pool组件,除去Java 2 SDK外,还需要先准备下列一些东西:
以上两种软件均有已编译包和源代码包两种形式可供选择。一般情况下,使用已编译包即可。不过建议同时也下载源代码包,作为参考资料使用。
如果打算使用源代码包自行编译,那么还需要准备以下一些东西:
具体的编译方法,可以参看有关的Ant文档。
将解压或编译后得到的commons-pool.jar和commons-collections.jar放入CLASSPATH,就可以开始使用Pool组件了。
PoolableObjectFactory、ObjectPool和ObjectPoolFactory
在Pool组件中,对象池化的工作被划分给了三类对象:
相应地,使用Pool组件的过程,也大体可以划分成“创立PoolableObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三种动作。
Pool组件利用PoolableObjectFactory来照看被池化的对象。ObjectPool的实例在需要处理被池化的对象的产生、激活、挂起、校验和销毁工作时,就会调用跟它关联在一起的PoolableObjectFactory实例的相应方法来操作。
PoolableObjectFactory是在org.apache.commons.pool包中定义的一个接口。实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现,需要根据情况自行创立。
创立PoolableObjectFactory的大体步骤是:
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) { 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); } |
最后完成的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); } } |
有了合适的PoolableObjectFactory之后,便可以开始请出ObjectPool来与之同台演出了。
ObjectPool是在org.apache.commons.pool包中定义的一个接口,实际使用的时候也需要利用这个接口的一个具体实现。Pool组件本身包含了若干种现成的ObjectPool实现,可以直接利用。如果都不合用,也可以根据情况自行创建。具体的创建方法,可以参看Pool组件的文档和源码。
ObjectPool的使用方法类似这样:
PoolableObjectFactory factory = new PoolableObjectFactorySample(); |
ObjectPool pool = new StackObjectPool(factory); |
Object obj = null; obj = pool.borrowObject(); |
pool.returnObject(obj); |
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接口还定义了几个可以由具体的实现决定要不要支持的操作,包括:
void clear()
清除所有当前在此对象池中休眠的对象。
int getNumActive()
返回已经从此对象池中借出的对象的总数。
int getNumIdle()
返回当前在此对象池中休眠的对象的数目。
void setFactory(PoolableObjectFactory factory)
将当前对象池与参数中给定的PoolableObjectFactory相关联。如果在当前状态下,无法完成这一操作,会有一个IllegalStateException异常抛出。
有时候,要在多处生成类型和设置都相同的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(); } } } } |
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++); } } |
可口可乐公司的软饮料有可口可乐、雪碧和芬达等品种,百事可乐公司的软饮料有百事可乐、七喜和美年达等类型,而Pool组件提供的ObjectPool实现则有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等种类。
不同类型的软饮料各有各自的特点,分别适应不同消费者的口味;而不同类型的ObjectPool也各有各自的特色,分别适应不同的情况。
StackObjectPool利用一个java.util.Stack对象来保存对象池里的对象。这种对象池的特色是:
StackObjectPool的构造方法共有六个,其中:
用不带factory参数的构造方法构造的StackObjectPool实例,必须要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。
这种对象池可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。
SoftReferenceObjectPool利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。这种对象池的特色是:
SoftReferenceObjectPool的构造方法共有三个,其中:
用不带factory参数的构造方法构造的SoftReferenceObjectPool实例,也要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。
这种对象池也可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。
GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是: