池化技术初探与Apache Pool代码分析
池化技术就是把一些经常使用的,创建起来又比较费时间的对象事先创建好放在内存中,这样在用的时候就可以直接从中取出以提高程序执行效率的策略,同时使用完比的对象还可以归还到池中,减少了一部分创建对象的时间。但是这种方式会带来一定的内存开销,尤其是对于那些生成时开销不大的对象,使用池化技术反而会是性能降低。因此就需要从程序的相应时间和内存空间的消耗上找到一个最佳契合点。
对于没有状态的对象(例如String),在重复使用之前,无需进行任何处理;对于有状态的对象(例如StringBuffer),在重复使用之前,就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制,恢复某个对象的状态的操作不可能实现了的话,就得把这个对象抛弃,改用新创建的实例了。
由于在同一时刻可能会有很多个线程同时向池请求对象,因此在池化技术实现的时候要时刻考虑多线程的问题。关于多线程技术在池化技术中的应用,请参考《多线程技术在池化技术的应用》一文。
Apache comman pool (以下简称“Pool组件”)是一个用于在java程序中实现对象池化的组件,它只包含两个包,涉及到的类较少,类之间的关系也相对简单。要想使用Pool组件,需要获取相关jar文件并将其放入CLASSPATH即可。为了研究其内部实现,我们可以下载其源代码,这些东西都可以从http://commons.apache.org/pool/download_pool.cgi下载,也可以从本篇文章的附件中获得。
Pool组件包含两个包:org.apache.commons.pool和org.apache.commons.pool.imp。包org.apache.commons.pool为对象池定义了一个简单的接口,和一些在实现对象池时可能会用到的类。这个包并没有实现对象池,但是却规定了为了有更好的移植性而必须要遵循的规范。而包org.apache.commons.pool.imp则提供了几种对象池的实现。
该包中所包含的所有类如下表所示:
所有接口 |
|
A "keyed" pooling interface. |
|
A factory for creating KeyedObjectPools. |
|
An interface defining life-cycle methods for instances to be served by a KeyedObjectPool. |
|
A pooling interface. |
|
A factory interface for creating ObjectPools. |
|
An interface defining life-cycle methods for instances to be served by an ObjectPool. |
所有类 |
|
A simple base implementation of KeyedObjectPool. |
|
A base implementation of KeyedPoolableObjectFactory. |
|
A simple base implementation of ObjectPool. |
|
A base implementation of PoolableObjectFactory. |
|
This class consists exclusively of static methods that operate on or return ObjectPool or KeyedObjectPool related interfaces. |
从上表中可以看出,出去最后一个PoolUtils工具类外,改包中的类可以分为两种,一种是带有“Keyed”的,另一种是没有带“Keyed”的。我们暂且不管第一种,只看没有带“Keyed”的五个类,这五个类的类图如下所示:
2.2.1三个接口
我们先从三个接口:PoolableObjectFactory、ObjectPool和ObjectPoolFactory说起。这三个使得pool组件可以以不同的方式创建并池化对象。是该包的核心也是整个Pool组件的核心。
1.PoolableObjectFactory:可池化对象工厂。该接口定义了一个可池化对象的生命周期,即创建对象,校验对象,激活对象和挂起对象,销毁对象等五个方法。其类图如下所示:
实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现,需要根据情况自行创立。
2. ObjectPool:对象池。这个接口定义了一个对象池必须实现的方法,例如向外部提供一个池中可用的对象,收回一个从外部返回的对象,关闭连接池等。其类图如下所示:
3.ObjectPoolFactory:对象池工厂。该接口用于生成连接池,它只定义了一个方法createPool(),其类图如下所示:
有的读者可能为问,当用到对象池时,直接New一个不就得了,为什么要通过ObjectPoolFactory这个工厂类呢?对于这个疑问,我个人的看法是这其实是一种工厂方法模式的应用,见《工厂方法模式的应用实例——创建不同的对象池》
2.2.2两个抽象类
接下来我们关注一下两个抽象类:BasePoolableObjectFactory和BaseObjectPool。PoolableObjectFactory定义了许多方法,可以适应多种不同的情况。但是,在并没有什么特殊需要的时候,直接实现PoolableObjectFactory接口,就要编写若干的不进行任何操作,或是始终返回true的方法来让编译通过,比较繁琐。这种时候就可以借助BasePoolableObjectFactory的威力,来简化编码的工作。BasePoolableObjectFactory是org.apache.commons.pool包中的一个抽象类。它实现了PoolableObjectFactory接口,并且为除了makeObject之外的方法提供了一个基本的实现――activateObject、passivateObject和destroyObject不进行任何操作,而validateObject始终返回true。通过继承这个类,而不是直接实现PoolableObjectFactory接口,就可以免去编写一些只起到让编译通过的作用的代码的麻烦了。这其实是适配器模式的简略形式(详见《适配器模式的使用范例》)BaseObjectPool的功能同BasePoolableObjectFactory类似,不再赘述。键值对对象池
有时候,单用对池内所有对象一视同仁的对象池,并不能解决的问题。例如,对于一组某些参数设置不同的同类对象――比如一堆指向不同地址的java.net.URL对象或者一批代表不同语句的java.sql.PreparedStatement对象,用这样的方法池化,就有可能取出不合用的对象的麻烦。
可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。但是,如果使用普通的ObjectPool来实施这个计策的话,因为普通的PoolableObjectFactory只能生产出大批设置完全一致的对象,就需要为每一组参数相同的对象编写一个单独的PoolableObjectFactory,工作量相当可观。这种时候就适合调遣Pool组件中提供的一种“带键值的对象池”来展开工作了。
Pool组件采用实现了KeyedObjectPool接口的类,来充当带键值的对象池。相应的,这种对象池需要配合实现了KeyedPoolableObjectFactory接口的类和实现了KeyedObjectPoolFactory接口的类来使用(这三个接口都在org.apache.commons.pool包中定义)。三个接口中定义的方法形式如出一辙,只是每个方法都增加了一个Object key参数而已。
这个包提供了五种对象池的实现,我们选择其中其中的一种——普通对象池进行简单分析,如果想了解更加详细的实现,请参考《Apache DBCP数据库连接池的实现》。这种对象池由以下两个类实现: GenericObjectPool和GenericObjectPoolFactory。
2.3.1GenericObjectPool
1. 属性
该类具有的属性有:
//对象池中处于挂起状态的最多对象数。 private int _maxIdle = DEFAULT_MAX_IDLE; //对象池中处于挂起状态的最少对象数。 private int _minIdle = DEFAULT_MIN_IDLE; //对象池中最多的处于可用状态的对象数。 private int _maxActive = DEFAULT_MAX_ACTIVE; //当对象耗尽时的状态为“锁定”且池内对象已被耗尽时,最多等待_maxWait毫秒后将抛出异常并锁定borrowObject方法。 private long _maxWait = DEFAULT_MAX_WAIT; //当池内对象被用尽之后采取的措施,默认为DEFAULT_WHEN_EXHAUSTED_ACTION(即WHEN_EXHAUSTED_BLOCK,锁定borrowObject方法) private byte _whenExhaustedAction = DEFAULT_WHEN_EXHAUSTED_ACTION; /** * 如果这个值为true,则当池中的某个对象被借出时将会被检验是否可用。 * 如果不可用,这个对象将被从池中剔除,同时向外借出另一个对象。 * 检验对象的具体方法由用户在实现PoolableObjectFactory接口的boolean validateObject(Object obj)方法中实现。 */ private volatile boolean _testOnBorrow = DEFAULT_TEST_ON_BORROW; /** * 如果这个值为true,则当池中的某个对象被归还时将会被检验是否可用。 * 如果不可用,这个对象将被从池中剔除。 * 检验对象的具体方法由用户在实现PoolableObjectFactory接口的boolean validateObject(Object obj)方法中实现。 */ private volatile boolean _testOnReturn = DEFAULT_TEST_ON_RETURN; /** * 如果这个值为true,则当池中的某个对象在挂起时将会被检验是否可用。 * 如果不可用,这个对象将被从池中剔除。 * 检验对象的具体方法由用户在实现PoolableObjectFactory接口的boolean validateObject(Object obj)方法中实现。 */ private boolean _testWhileIdle = DEFAULT_TEST_WHILE_IDLE; //两次被定时检验线程检验是否可用之间的毫秒数,如果为负数,则永远不会被检验。 private long_timeBetweenEvictionRunsMillis=DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; //默认的每次被检验线程检验是否可用的对象数目。 private int _numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN; //池内处于闲置状态的对象被“清理”线程移除之前可以闲置在对象池内的最短时间。 private long _minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; //在保证池内至少有“minIdle”个闲置对象的前提下,被“清理”线程移除之前可以闲置在对象池内的最短时间。 Private long_softMinEvictableIdleTimeMillis=DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS; //借出对象的顺序,默认借出最近归还的一个对象。 private boolean _lifo = DEFAULT_LIFO; //CursorableLinkedList是一个类似于java.util.LinckedList的数据结构,用于存数对象池中的对象。 private CursorableLinkedList _pool = null; //用于记录闲置对象清理器当前的位置。 private CursorableLinkedList.Cursor _evictionCursor = null; // 可池化对象工厂,是一个实现了PoolableObjectFactory接口的工厂类,该类根据池中所存放对象不同而不同,由用户自行实现。 private PoolableObjectFactory _factory = null; // 已经借出但是尚未归还的对象,这个对象正在被使用中。 private int _numActive = 0; // 对象“清理器”,是一个定时执行的对象,用以移除池中该移除的对象。 private Evictor _evictor = null; // 处于内部处理过程(比如被创建或者被销毁)中的对象数目,这些对象应该在总数中,但是既不处于活动状态也不属于闲置状态。 private int _numInternalProcessing = 0; // 用以维护多个访问borrowObject()方法以借用池中对象的线程的访问顺序,并按照此顺寻分配线程,保证先到先得。 private final LinkedList _allocationQueue = new LinkedList();
同时该类还为以上属性提供了一些默认值,如下所示:
// 当对象池中对象被用完时(处于活动的对象数目已达到最大值)的措施标志——抛出异常 public static final byte WHEN_EXHAUSTED_FAIL = 0; //当对象池中对象被用完时(处于活动状态的对象数目已达到最大值)的措施标志——锁定borrow方法,直到有新的 可用对象生成。 public static final byte WHEN_EXHAUSTED_BLOCK = 1; // 当对象池中对象被用完时(处于活动状态的对象数目已达到最大值)的措施标志——生成新的对象并返回。 public static final byte WHEN_EXHAUSTED_GROW = 2; // 默认的最大处于空闲状态的对象数目。 public static final int DEFAULT_MAX_IDLE = 8; // 默认的最小处于空闲状态的对象数目。 public static final int DEFAULT_MIN_IDLE = 0; // 默认的最大处于活动状态的对象数目。 public static final int DEFAULT_MAX_ACTIVE = 8; // 默认的池内对象耗尽时的措施——锁定borrow方法。 public static final byte DEFAULT_WHEN_EXHAUSTED_ACTION = WHEN_EXHAUSTED_BLOCK; // 默认的借出顺序,借出最近归还的一个对象。 public static final boolean DEFAULT_LIFO = true; // 默认最长等待时间,如果超过这个时间后borrow方法任然不能返回一个对象,则抛出一个异常。 public static final long DEFAULT_MAX_WAIT = -1L; // 默认在借出时不检查是否可用。 public static final boolean DEFAULT_TEST_ON_BORROW = false; // 默认在归还时不检查是否可用。 public static final boolean DEFAULT_TEST_ON_RETURN = false; // 默认在挂起时不检查是否可用。 public static final boolean DEFAULT_TEST_WHILE_IDLE = false; // 默认两次被定时检验线程检验是否可用之间的毫秒数,如果为负数,则永远不会被检验 public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L; // 默认的每次被检验线程检验是否可用的对象数目。 public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3; // 池内处于闲置状态的对象被“清理”线程移除之前可以闲置在对象池内的最短时间。 public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L; // 在保证池内至少有“minIdle”个闲置对象的前提下,被“清理”线程移除之前可以闲置在对象池内的最短时间。 public static final long DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1;
2. 构造方法
这个类提供了两种构造方法,第一种是通过传值为其属性或者部分属性赋值这种普通的方式来构造对象,例如:
public GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait,int maxIdle, int minIdle, boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, boolean testWhileIdle, long softMinEvictableIdleTimeMillis, boolean lifo) { _factory = factory; _maxActive = maxActive; _lifo = lifo; switch(whenExhaustedAction) { case WHEN_EXHAUSTED_BLOCK: case WHEN_EXHAUSTED_FAIL: case WHEN_EXHAUSTED_GROW: _whenExhaustedAction = whenExhaustedAction; break; default: throw new IllegalArgumentException("whenExhaustedAction " + whenExhaustedAction + " not recognized."); } _maxWait = maxWait; _maxIdle = maxIdle; _minIdle = minIdle; _testOnBorrow = testOnBorrow; _testOnReturn = testOnReturn; _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; _numTestsPerEvictionRun = numTestsPerEvictionRun; _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; _softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis; _testWhileIdle = testWhileIdle; _pool = new CursorableLinkedList(); startEvictor(_timeBetweenEvictionRunsMillis); }
这两个方法实现了向连接池中放入对象。其中GenericObjectPool.Config config的作用就是把该类的属性封装起来,该内部类的源代码很简单,再次不在展示。这样可以作为一个整体传入,虽然依据《重构既有代码》中所说,这也不是一个好办法,但这避免了第一种构造法方法中超长列表的出现。
3. 几个重要方法
uvoid addObject()和addObjectToPool()
其核心代码如下所示:
…… …… //调用由用户实现的可池化对象工厂生成一个可池化对象 Object obj = _factory.makeObject(); …… …… //将该对象添加到对象池中 addObjectToPool(obj, false); …… ……
uborrowObject()向访问对象池的线程返回一个对象:
public Object borrowObject() throws Exception { ………… //根据不同的参数组合做不同的处理,由于代码逻辑比较复杂,在此不在赘述,请参看《一个多线程技术的应用实例》 switch(whenExhaustedAction) { case WHEN_EXHAUSTED_GROW: ………… break; case WHEN_EXHAUSTED_FAIL: ………… break; case WHEN_EXHAUSTED_BLOCK: ………… break; //最终返回一个池中的对象,lantch是一个特殊的键值对的数据结构,其中有一个数据元素用于存储其请求到的对象。 return latch.getPair().value; …………
uinvalidateObject()废弃池中的对象
public void invalidateObject(Object obj) throws Exception { ……………… //调用池中对象对应的工厂类,废弃池中的对象,具体实现由用户完成。 _factory.destroyObject(obj); ……………… }
ureturnObject(Object obj)() 向池中归还借出的对象addObjectToPool(Object obj, boolean decrementNumActive)
public void returnObject(Object obj)和 throws Exception { ……………… addObjectToPool(obj, true); ……………… } private void addObjectToPool(Object obj, boolean decrementNumActive) throws Exception { //如果在相池中加入对象时需要检验是否可用,则先进行检验。 boolean success = true; if(_testOnReturn && !(_factory.validateObject(obj))) { success = false; } else { _factory.passivateObject(obj); } ………… //根据设定的“先入先出”还是“先入后出”模式向池中添加对象ObjectTimestampPair是一个用于存储池中//对象的数据结构 if (_lifo) { _pool.addFirst(new ObjectTimestampPair(obj)); } else { _pool.addLast(new ObjectTimestampPair(obj)); } …………
该类用于生成GenericObjectPool对象,此类封装了GenericObjectPool的所有属性,并将其默认值设置为GenericObjectPool的属性的默认值,其核心方法为ObjectPool createPool(),代码如下:
public ObjectPool createPool() { return new GenericObjectPool(_factory,_maxActive,_whenExhaustedAction,_maxWait,_maxIdle,_minIdle,_testOnBorrow,_testOnReturn,_timeBetweenEvictionRunsMillis,_numTestsPerEvictionRun,_minEvictableIdleTimeMillis,_testWhileIdle,_softMinEvictableIdleTimeMillis,_lifo); }
当然,如果不想使用默认值,我们可以将其属性设置为其他值,这样在构造同类型同参数的对象池时,直接调用上述方法就可以了。
(本文章引用了http://www.ibm.com/developerworks/cn/java/l-common-pool/index.html中的部分内容。)