commons-pool对象池实现原理及使用(一)

来源:互联网 发布:转口贸易产生海关数据 编辑:程序博客网 时间:2024/05/19 20:42

我们知道频繁的创建对象是一个相对较重的过程,jvm要加载类,初始化对象,为对象分配内存涉及到多个系统调用,这样对一个高负载的系统来说是比较消耗资源的。这时我们可以将所需对象进行池化,apache开源的的commons-pool是一个比较经典的实现对象池化组件,笔者所在的公司在多个框架中都使用过这个组件,还有很多业界知名的中间件如Redis客户端Jedis等都使用到了这个池化组件。我们虽然不必重复造轮子,但是我们也得了解这个轮子是怎么造的。

接下来我们就从commons-pool的类层次设计、实现原理、使用这三个方面来研究一下commons-pool,我们这里以commons-pool-1.6源码进行分析,源码和jar包可以在这个链接中下载commons-pool源码和jar包官方下载。

首先我们先解释如下几个概念:

对象池:就是我们要介绍的commons-pool的核心概念,类似于容器,用于存放我们需要池化的对象

对象池工厂:就是操作对象池的工程(类似于设计模式中的工程模式),这个工厂可以执行借出、归还、销毁等管理池对象的操作

池对象:就是我们需要池化的对象

池对象工厂:就是操作池对象的工厂,比如生成、验证、激活、钝化、销毁池对象

(1)commons-pool类的层次设计


这个是Commons-pool的源码包,只有两个包org.apache.commons.pool这个包下大部分都是接口和抽象类,org.apache.commons.pool.impl这个是用户可以直接使用的接口或抽象类的具体实现。

public class GenericObjectPool<T> extends BaseObjectPool<T> implements ObjectPool<T>

ObjectPool是一个顶层接口,里面定义了对象池的一些基本操作,BaseObjectPool是一个抽象类对上层接口提供了缺省实现,作为一个用户层的默认缓冲实现,这样用户就不必全部实现顶层接口的所有方法,这种缺省模式在很多地方都可以使用,GenericObjectPool是一个具体的对象池的实现。

public interface ObjectPool {//从对象池中借出对象public abstract Object borrowObject() throws Exception,NoSuchElementException, IllegalStateException;//将对象归还到对象池中public abstract void returnObject(Object obj) throws Exception;//使一个对象无效public abstract void invalidateObject(Object obj) throws Exception;//向对象池中添加对象public abstract void addObject() throws Exception, IllegalStateException,UnsupportedOperationException;//得到当前对象池中空闲的对象public abstract int getNumIdle() throws UnsupportedOperationException;//得到当前对象池已经借出的对象,这里的激活其实就是对象池借出对象的概念public abstract int getNumActive() throws UnsupportedOperationException;//清空对象池,并销毁对象池中的所有对象public abstract void clear() throws Exception,UnsupportedOperationException;// 关闭对象池public abstract void close() throws Exception;/** * @deprecated Method setFactory is deprecated *这个方法已经弃用了,池对象工程需要在实现类的构造函数中传入 */public abstract void setFactory(PoolableObjectFactory poolableobjectfactory)throws IllegalStateException, UnsupportedOperationException;}

public abstract class BasePoolableObjectFactory<T> implements PoolableObjectFactory<T>
这个是池对象类的设计继承结构,PoolableObjectFactory<T>这是一个池对象的顶层接口,BasePoolableObjectFactory<T>这是一个抽象类提供了一些缺省实现,我们在使用的时候即可以继承BasePoolableObjectFactory<T>这个抽象类,也可以实现顶层接口PoolableObjectFactory<T>。

public interface PoolableObjectFactory {/** * 对象池调用该方法 创建一个对象 */public abstract Object makeObject() throws Exception;/** * 归还对象时 如果调用validateObject 验证对象失败,或者连接池被关闭, * 又或者归还对象时连接池中空闲的对象数量大于等于MaxIdel * 若符合上述三种情况之一 都会调用这个方法销毁对象 * 在驱逐线程启动进行检查是符合驱逐策略也会调用这个方法销毁对象 */public abstract void destroyObject(Object obj) throws Exception;/** * testOnBorrow设置为true时,创建对象时调用该方法验证对象的有效性, * 如果无效直接抛出异常throw new Exception("ValidateObject failed"); * testOnReturn设置为true时,归还对象时 验证对象是否还有效 比如连接是否还在, * 如果无效了则不往对象池中放对象。 */public abstract boolean validateObject(Object obj);/** * 调用该方法激活对象 *  */public abstract void activateObject(Object obj) throws Exception;/** * 归还对象时 钝化对象 比如某些对象用完之后需要休眠一段时间 */public abstract void passivateObject(Object obj) throws Exception;}

(二)Commons-pool实现原理

commons-pool对象池暴露出来很多参数供用户配置使用,下面就是对象池暴露的参数以及默认值。

   public static class Config {        /**         * 对象池中最多可以空闲的数量,如果设置的太小,当负载很重时线程借对象的时候的时候会频繁创建对象         * 这个值其实可以理解为 在对象池中缓存多少个对象 当需要的对象个数大于maxIdle时再创建新对象         */        public int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;        /**         * 对象池中保留对象的最小的个数,设置为0 代表对象都可以外借         */        public int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE;        /**         * 这个值就是对象池的最大可以外借的数量         * 负数不限制 对象池可以外接的对象数量         */        public int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;        /**         * 当对象池外借对象数量大约maxActive时 再来借对象的线程将会阻塞的时间         */        public long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;        /**         * 当对象池外借对象数量大约maxActive时 再来借对象的线程时的行为         * 0 失败;1阻塞;2继续创建新对象         */        public byte whenExhaustedAction = GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION;        /**         * true 借对象时调用池对象工厂的验证方法 验证对象的有效性         */        public boolean testOnBorrow = GenericObjectPool.DEFAULT_TEST_ON_BORROW;        /**         * true 返还对象时调用池对象工厂的验证方法 验证对象的有效性         */        public boolean testOnReturn = GenericObjectPool.DEFAULT_TEST_ON_RETURN;        /**         * true 驱逐线程在对池中对象进行 检测时会对池对象进行validateObject 验证         * 如果验证返回false,则将会销毁该对象         */        public boolean testWhileIdle = GenericObjectPool.DEFAULT_TEST_WHILE_IDLE;        /**         * 驱逐线程运行的时间间隔         */        public long timeBetweenEvictionRunsMillis = GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;        /**         * 驱逐线程 每次执行驱逐的对象个数         */        public int numTestsPerEvictionRun =  GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;        /**         * 池对象在对象池中的时间如果小于这个正数值 则不进行驱逐 ;大于这个值则进行驱逐检测操作         * 如果这个值小于0 则不进行驱逐         */        public long minEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;        /**         * 池对象在对象池中的时间如果小于这个正数值 则不进行驱逐;大于这个值则进行驱逐检测操作,但是至少要保留minIdel对象在池中         * 如果这个值小于0 则不进行驱逐         */        public long softMinEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;        /**         * true 对象池外借对象时按照对象入池是lifo的顺序外借         * false 对象池外借对象时按照对象入池是fifo的顺序外借         */        public boolean lifo = GenericObjectPool.DEFAULT_LIFO;    }


这里以GenericObjectPool<T>这个具体的实现来分析,据我分析代码来看这个类中最重要的三个成员域就是如下三个:

    /** My pool.  *这个就是对象池 是一个基于游标的双向链表,空闲的池对象都会放到这里面, *如果设置了驱逐策略,驱逐线程会定期检测这里面空闲对象是否可以驱逐 */    private CursorableLinkedList<ObjectTimestampPair<T>> _pool = null;    /** Eviction cursor - keeps track of idle object evictor position  *这个是驱逐线程使用,保证驱逐对象的顺序不变 */    private CursorableLinkedList<ObjectTimestampPair<T>>.Cursor _evictionCursor = null;/**     * Used to track the order in which threads call {@link #borrowObject()} so     * that objects can be allocated in the order in which the threads requested     * them. * 这是一个双向链表,用于存储用户线程的借对象的请求,主要是保证对外借对象的公平性, * 如果多个线程阻塞,当对象池内有对象可用时,解除阻塞线程的顺序就是这个_allocationQueue域 * 保证的顺序     */    private final LinkedList<Latch<T>> _allocationQueue = new LinkedList<Latch<T>>();

接下来我们分析对象池中核心的三个方法,由于对象池是针对多线程使用的,大量方法都有加锁保持数据同步。GenericObjectPool<T> 类内部有一个存储外部线程请求的数据结构 private static final class Latch<T>,这是一个私用的静态内部类。

/**     * 为_allocationQueue队列中的请求分配对象,如果对象池中有对象则优先分配对象池中的对象,     * 如果对象池中没有对象,在满足当前可接对象小于最大可激活对象的情况下设置可创建对象标记,     * 并解除一个阻塞的线程     */    private synchronized void allocate() {        if (isClosed()) return;        // 如果对象池中有对象,优先分配对象池中的对象        //_numInternalProcessing这个表示当前正在处理的对象,这个对象的创建成功之后会计入激活对象的数量        for (;;) {            if (!_pool.isEmpty() && !_allocationQueue.isEmpty()) {                Latch<T> latch = _allocationQueue.removeFirst();                latch.setPair( _pool.removeFirst());                _numInternalProcessing++;                synchronized (latch) {                    latch.notify();                }            } else {                break;            }        }        // 如果对象池中没有可以获得对象,那就从请求队列中获取一个请求,解除响应的线程阻塞,创建一个新对象        // 当然条件就是满足_numActive + _numInternalProcessing<_maxActive        for(;;) {            if((!_allocationQueue.isEmpty()) && (_maxActive < 0 || (_numActive + _numInternalProcessing) < _maxActive)) {                Latch<T> latch = _allocationQueue.removeFirst();                latch.setMayCreate(true);                _numInternalProcessing++;                synchronized (latch) {                    latch.notify();                }            } else {                break;            }        }    }

    /**     * 从对象池中接一个对象,这个方法代码看起来很乱,但是逻辑是很清楚的     * (1)对象池中没有对象,一种情况是对象都外借了此时达到了maxActive限制的数量,对象池处于耗尽的状态,     *    这时执行_whenExhaustedAction设置的行为,等待归还对象。     *    另一种情况是当前外借对象小于maxActive,且没有闲置的对象,这时设置可创建标志,对象池创建对象,并调用池对象工程激活和验证对象。     * (2)对象池中有对象,调用池对象工程方法激活并验证对象直接返回     */@Overridepublic T borrowObject() throws Exception {long starttime = System.currentTimeMillis();Latch<T> latch = new Latch<T>();byte whenExhaustedAction;long maxWait;synchronized (this) {//分配本地变量副本whenExhaustedAction = _whenExhaustedAction;maxWait = _maxWait;// 将借对象请求添加到请求队列_allocationQueue.add(latch);}//执行分配对象方法,主要就是根据当前对象池的具体情况,设置请求latch的_pair和_mayCreate的状态allocate();for (;;) {synchronized (this) {assertOpen();}// 对象池中没有对象可以分配if (latch.getPair() == null) {// 当前外借对象小于maxActive时,可以创建新对象if (latch.mayCreate()) {// allow new object to be created} else {// 如果没有分配到对象,且当前不能创建新对象,表名当前对象池处于耗尽状态switch (whenExhaustedAction) {case WHEN_EXHAUSTED_GROW:// 允许对象增长synchronized (this) {//将请求移出请求队列 之后在后面创建对象if (latch.getPair() == null && !latch.mayCreate()) {_allocationQueue.remove(latch);_numInternalProcessing++;}}break;case WHEN_EXHAUSTED_FAIL:synchronized (this) {//如果期间已经分配了对象,则跳出调用池对象工场激活 验证对象//如果没有移出请求 抛出异常if (latch.getPair() != null || latch.mayCreate()) {break;}_allocationQueue.remove(latch);}throw new NoSuchElementException("Pool exhausted");case WHEN_EXHAUSTED_BLOCK:try {synchronized (latch) {//阻塞线程if (latch.getPair() == null&& !latch.mayCreate()) {if (maxWait <= 0) {latch.wait();} else {// this code may be executed again after// a notify then continue cycle// so, need to calculate the amount of// time to waitfinal long elapsed = (System.currentTimeMillis() - starttime);final long waitTime = maxWait - elapsed;if (waitTime > 0) {latch.wait(waitTime);}}} else {break;}}// see if we were awakened by a closing poolif (isClosed() == true) {throw new IllegalStateException("Pool closed");}} catch (InterruptedException e) {boolean doAllocate = false;synchronized (this) {// Need to handle the all three possibilitiesif (latch.getPair() == null&& !latch.mayCreate()) {// Case 1: latch still in allocation queue// Remove latch from the allocation queue_allocationQueue.remove(latch);} else if (latch.getPair() == null&& latch.mayCreate()) {// Case 2: latch has been given permission// to create// a new object_numInternalProcessing--;doAllocate = true;} else {// Case 3: An object has been allocated_numInternalProcessing--;_numActive++;returnObject(latch.getPair().getValue());}}if (doAllocate) {allocate();}Thread.currentThread().interrupt();throw e;}if (maxWait > 0&& ((System.currentTimeMillis() - starttime) >= maxWait)) {synchronized (this) {// Make sure allocate hasn't already assigned an// object// in a different thread or permitted a new// object to be createdif (latch.getPair() == null&& !latch.mayCreate()) {// Remove latch from the allocation queue_allocationQueue.remove(latch);} else {break;}}throw new NoSuchElementException("Timeout waiting for idle object");} else {continue; // keep looping}default:throw new IllegalArgumentException("WhenExhaustedAction property "+ whenExhaustedAction+ " not recognized.");}}}//如果没有分配对象 则调用池对象工厂方法 创建 对象boolean newlyCreated = false;if (null == latch.getPair()) {try {T obj = _factory.makeObject();latch.setPair(new ObjectTimestampPair<T>(obj));newlyCreated = true;} finally {if (!newlyCreated) {// object cannot be createdsynchronized (this) {_numInternalProcessing--;// No need to reset latch - about to throw exception}allocate();}}}// 则调用池对象工厂方法 激活 验证对象try {_factory.activateObject(latch.getPair().value);if (_testOnBorrow&& !_factory.validateObject(latch.getPair().value)) {throw new Exception("ValidateObject failed");}synchronized (this) {_numInternalProcessing--;_numActive++;}return latch.getPair().value;} catch (Throwable e) {PoolUtils.checkRethrow(e);// object cannot be activated or is invalidtry {_factory.destroyObject(latch.getPair().value);} catch (Throwable e2) {PoolUtils.checkRethrow(e2);// cannot destroy broken object}synchronized (this) {_numInternalProcessing--;if (!newlyCreated) {latch.reset();_allocationQueue.add(0, latch);}}allocate();if (newlyCreated) {throw new NoSuchElementException("Could not create a validated object, cause: "+ e.getMessage());} else {continue; // keep looping}}}}


原创粉丝点击