对象池(连接池):commons-pool2源码解析:GenericObjectPool的returnObject方法解析

为什么会有对象池

在实际的应用工程当中,存在一些被频繁使用的、创建或者销毁比较耗时、持有的资源也比较昂贵的一些对象。比如:数据库连接对象、线程对象。所以如果能够通过一种方式,把这类对象统一管理,让这类对象可以被循环利用的话,就可以减少很多系统开销(内存、CPU、IO等),极大的提升应用性能。

Apache Commons Pool

Apache Commons Pool就是一个对象池的框架,他提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。Apache Commons Pool是很多连接池实现的基础,比如DBCP连接池、Jedis连接池等。
Apache Commons Pool有个两个大版本,commons-pool和commons-pool2。commons-pool2是对commons-pool的重构,里面大部分核心逻辑实现都是完全重写的。我们所有的源码分析都是基于commons-pool2。

在commons-pool2中,对象池的核心接口叫做ObjectPool,他定义了对象池的应该实现的行为。

  • addObject方法:往池中添加一个对象。池子里的所有对象都是通过这个方法进来的。
  • borrowObject方法:从池中借走到一个对象。借走不等于删除。对象一直都属于池子,只是状态的变化。
  • returnObject方法:把对象归还给对象池。归还不等于添加。对象一直都属于池子,只是状态的变化。
  • invalidateObject:销毁一个对象。这个方法才会将对象从池子中删除,当然这其中最重要的就是释放对象本身持有的各种资源。
  • getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。
  • getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。
  • clear:清理对象池。注意是清理不是清空,改方法要求的是,清理所有空闲对象,释放相关资源。
  • close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。

在commons-pool2中,ObjectPool的核心实现类是GenericObjectPool。

在前面的文章中我已经解析过addObject和borrowObject方法的实现,对应链接地址:

  • addObject方法解析:https://blog.csdn.net/weixin_42340670/article/details/107749058
  • borrowObject方法解析:https://blog.csdn.net/weixin_42340670/article/details/106876879

本文接着解析returnObject方法在GenericObjectPool中的实现。

在讨论具体实现之前,我们还有必要看一下该方法在ObjectPool接口的具体定义是如何描述的。

ObjectPool接口中returnObject解析

/**
* Return an instance to the pool. By contract, <code>obj</code>
* <strong>must</strong> have been obtained using {@link #borrowObject()} or
* a related method as defined in an implementation or sub-interface.
*
* @param obj a {@link #borrowObject borrowed} instance to be returned.
*
* @throws IllegalStateException
*              if an attempt is made to return an object to the pool that
*              is in any state other than allocated (i.e. borrowed).
*              Attempting to return an object more than once or attempting
*              to return an object that was never borrowed from the pool
*              will trigger this exception.
*
* @throws Exception if an instance cannot be returned to the pool
*/

/**
 * 把一个实例(对象)归还给对象池。
 * 按照约定,obj也就是要归还的对象一定是通过borrowObject方法、
 * 或者是实现类或者子接口中的和borrowObject有一定逻辑关系的方法获得的(返回的)的对象。
 *
 * obj 通过 borrowObject借走的,现在要归还的对象。
 *
 * 如果要归还的对象的状态不是allocated状态、或者要归还的对象是一个从来没有被借走的对象。
 * 那么就会触发IllegalStateException异常,标识非法状态。
 *
 * 如果是其他情况导致的不能归还,抛出Exception异常。
 */
void returnObject(T obj) throws Exception;

接口的注释,往往对于逻辑实现都给出了一些明确的指示和要求,比如说:

  • returnObject和borrowObject应该成对的互斥操作。
  • 所谓的borrowObject不是从池子里真的移除。
  • 所谓的returnObject也不是真的往池子里添加。
  • 对象一直都在大池子里,是用状态来区分是被借走了、还是已经归还了(空闲了)

接下来我们再详细的看一下GenericObjectPool对于returnObject的具体实现,是否满足注释中的说法或者要求。

GenericObjectPool类中returnObject解析

public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
        implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {
   
     /*
     * All of the objects currently associated with this pool in any state. It
     * excludes objects that have been destroyed. The size of
     * {@link #allObjects} will always be less than or equal to {@link
     * #_maxActive}. Map keys are pooled objects, values are the PooledObject
     * wrappers used internally by the pool.
     */
    /*
     * allObjects 是一个ConcurrentHashMap,里面存储的就是对象池中所有的对象,ConcurrentHashMap能够保证并发读写安全。
     * 这里面不包括已经被销毁的对象,也就是说被销毁的对象会从allObjects中被移除掉。allObjects的数量应该小于等于maxActive(最大活跃对象数量)的值。
     * ConcurrentHashMap的key是IdentityWrapper类型的对象,他可以包装任何对象T,在这里他包装的T的实例就是业务上产生的最直接的对象(数据库连接对象、线程对象等)。
     * ConcurrentHashMap的value是PooledObject类型的对象,他持有的T的实例也是业务上产生的最直接的对象(数据库连接对象、线程对象等)。
     * 关于IdentityWrapper和PooledObject这两个类(接口)的作用,我们在之前的《addObject方法解析中》有详细解释。
     * addObject方法解析链接:https://blog.csdn.net/weixin_42340670/article/details/107749058
     */ 
    private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects =
        new ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>();

    /*
     * 用一个链表结构的阻塞队列来存储空闲对象
     */
    private final LinkedBlockingDeque<PooledObject<T>> idleObjects;

    /*
     * 对象工厂,GenericObjectPool的实现依赖了PooledObjectFactory来生产对象
     */
    private final PooledObjectFactory<T> factory;


    /**
     * {@inheritDoc}
     * <p>
     * If {@link #getMaxIdle() maxIdle} is set to a positive value and the
     * number of idle instances has reached this value, the returning instance
     * is destroyed.
     * <p>
     * If {@link #getTestOnReturn() testOnReturn} == true, the returning
     * instance is validated before being returned to the idle instance pool. In
     * this case, if validation fails, the instance is destroyed.
     * <p>
     * Exceptions encountered destroying objects for any reason are swallowed
     * but notified via a {@link SwallowedExceptionListener}.
     */

     /**
      * 虽然这个方法的作用是把一个不再使用的对象交还给对象池。(对象一直在池子里,所谓的还,就是变更对象的状态而已)
      * 但是,如果maxIdle(最大空闲数量)设置为一个有效的正数值,并且对象池里空闲的对象数量已经达到这个数值了,那么这个要还给对象池的对象可以直接被销毁。
      * 可以正常返还到对象池的情况下,如果testOnReturn设置为true,那么这个要交还的对象在重新入池之前需要校验有效性,如果校验失败,则直接销毁。
      * 在销毁对象时产生的任何异常都会被吞下,并交给SwallowedExceptionListener去处理。
      */
    @Override
    public void returnObject(T obj) {
        // 对象池allObjects是一个Map,他需要一个IdentityWrapper类型的key来获取出包装了原始对象的PooledObject对象。
        // IdentityWrapper的核心作用,是为了消除原始对象(业务对象)T的可能重写的hashcode和equals方法带来的影响。
        PooledObject<T> p = allObjects.get(new IdentityWrapper<T>(obj));
        
        if (p == null) { // 如果p为空,说明这个要还的对象,已经不在池子中了。
            // 不在池子中了,说明有可能被回收了,一个被使用的对象,是如何被回收的呢?
            // 虽然对象状态一直是被使用,如果超过一定时间,也是可能被强制回收的。这就取决于abandonedConfig是如何配置的了。
            // 如果没有配置abandonedConfig,说明就不是被强制回收的,那可能出了什么其他问题,说不清道不明的话,就抛个异常吧。
            if (!isAbandonedConfig()) { 
                throw new IllegalStateException(
                        "Returned object not currently part of this pool");
            } else { // 如果配置了abandonedConfig,那说明应该就是被强制回收了,也算是在计划范围内,所以直接return。
                return; // Object was abandoned and removed
            }
            // 关于abandonedConfig希望能够参考我的另一篇解析,详细了解。
            // abandonedConfig解析:https://blog.csdn.net/weixin_42340670/article/details/107136994
        }

        // 如果p不为空,这里加上了同步锁,保证内部逻辑处理的原子性。因为涉及到最重要的状态判断和变更。
        synchronized(p) { 
            final PooledObjectState state = p.getState(); // 首先获取对象状态
            // 要归还的对象状态就一定应该是ALLOCATED,标识使用中,如果状态不是ALLOCATED,就说明状态不符合预期,抛出个异常。
            if (state != PooledObjectState.ALLOCATED) { 
                throw new IllegalStateException(
                        "Object has already been returned to this pool or is invalid");
            } else {  // 如果状态符合预期
                // 调用PooledObject对象的markReturning方法进行对象状态变更
                // PooledObject的默认实现类DefaultPooledObject的该方法只有一行代码:
                //    state = PooledObjectState.RETURNING; //就是改变一下状态。
                p.markReturning(); 
            }
        }

        long activeTime = p.getActiveTimeMillis(); // 获取对象被使用了多长时间

        if (getTestOnReturn()) { // 如果testOnReturn配置为true,说明还需要校验下有效性

            /*
            调用PooledObjectFactory工厂方法validateObject进行校验对象的有效性,校验失败则返回false
            这个方法,不同的factory有不同的实现方式
            1、大多数据库连接池(比如dbcp的工厂实现PoolableConnectionFactory),默认会发一个select 1语句,校验连接的有效性。
            2、jedis的工厂实现类是JedisFactory,对于该方法的实现是发一条redis的ping命令来校验连接的有效性。
            有的工厂实现捕捉到异常,会直接抛出;有的实现捕捉到异常会忽略,但是最终还是会返回false。
            */     
            if (!factory.validateObject(p)) { 
                try {
                    destroy(p); // 如果校验不通过,则销毁该对象
                } catch (Exception e) {
                    // 如果销毁产生异常,调用swallowException方法,交给SwallowedExceptionListener去处理(如果存在的话)
                    swallowException(e); 
                }
                try {
                    // 如果当前正存在着调用方正在等待获取空闲对象,那么就需要创建一个空闲对象。
                    ensureIdle(1, false); // 这个方法下面会单独解析
                } catch (Exception e) {
                    swallowException(e); // 如果创建对象产生异常,也一样交给SwallowedExceptionListener处理
                }

                // 这个方法主要用来更新一些统计信息:对象池的归还操作一共执行了多少次,最近100个被归还对象的使用时长
                updateStatsReturn(activeTime); 
                return; 
            }
        }

        // 如果能够执行到这里,说明该对象是有效的
        try {
            /*
             passivateObject方法按照PooledObjectFactory接口中对其职责的描述应该叫做:对一个对象进行反初始化,什么意思?
             一个对象从使用中变为空闲,这个时候他本身除了持有很多核心资源外,可能还挂载了其他一些附加轻量的资源或者属性。
             对象池的目的就是为了避免重复的创建这种持有昂贵资源的对象,所以即使是对象状态变为空闲,但是本身的核心资源也不能被释放。
             但是那些外围的轻量的资源和属性是可以被卸载和重置的。所以可以这么理解,passivateObject方法是在对象变为空闲时,重置或者卸载那些非核心的资源和属性的。
             那怎么区分核心和非核心呢? 这个事情是使用方要操心的事情,你如果觉得你的对象持有的都是核心资源,passivateObject你还必须实现,所以你可以再方法内部啥逻辑都不写。其实JedisFactory就是这么干的。

             和passivateObject对应的是activateObject方法,activateObject意思是一个对象在被调用方使用前重新初始化一些附加信息。
             JedisFactory对于activateObject提供了实现,内部逻辑做了redis dbindex的修正动作。
            */
            factory.passivateObject(p);
        } catch (Exception e1) {  // 如果反初始化异常,和上面的解析近乎相同的套路:异常甩锅 + 销毁对象 + 创建空闲对象(基于判定情况) + 更新统计信息
            swallowException(e1); // 异常甩锅
            try {
                destroy(p); // 销毁对象
            } catch (Exception e) {
                swallowException(e); // 异常甩锅
            }
            try {
                ensureIdle(1, false); // 创建空闲对象(基于判定情况)
            } catch (Exception e) {
                swallowException(e); // 异常甩锅
            }
            updateStatsReturn(activeTime); // 更新统计信息
            return;
        }

        // 如果能执行到这里,说明反初始化(轻量级资源卸载)也成功了。

        /*
         deallocate 意思是 de-allocate.
         再回顾一下上面的逻辑,能走到这里,说明p的当前状态为:RETURNING
         调用deallocate目的是把状态更改为:IDLE
         在<RETURNING>和<IDLE>状态过渡的中间,执行了validate、passivate等校验和清理动作。
         如果deallocate返回false,说明状态变更失败,抛出个异常:说明一下对象可能已经是空闲或者失效状态了,这两种状态不支持变更为空闲状态
        */ 
        if (!p.deallocate()) { 
            throw new IllegalStateException(
                    "Object has already been returned to this pool or is invalid");
        }

        // 能执行到这里,说明状态也成功变为IDLE了

        int maxIdleSave = getMaxIdle(); // 获取对象池配置的最大空闲对象数量
        // 这个判断的意思就是,一个正常状态的对象池,目前空闲对象数量已经达到规定的最大值(正数)了,也就没必要再加多余的空闲了,可以直接把还回来的销毁掉。
        if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
            try {
                destroy(p);  // 销毁
            } catch (Exception e) {
                swallowException(e); // 异常甩锅
            }
        } else { // 如果对象可以被正常归还,那么把对象添加到空闲队列
            if (getLifo()) { // 获取对象池的优先级配置
                idleObjects.addFirst(p); // 如果是后进先出,那么把空闲对象添加到队列开头
            } else {
                idleObjects.addLast(p); // 如果是先进先出,那么把空闲对象添加到队列末尾
            }
            if (isClosed()) { // 判断一下对象池状态,如果是关闭状态,那么调用clear方法,清空对象池
                // Pool closed while object was being added to idle objects.
                // Make sure the returned object is destroyed rather than left
                // in the idle object pool (which would effectively be a leak)
                clear();
            }
        }
        updateStatsReturn(activeTime); // 更新统计信息
    }
}

在上面的方法解析中,提到了如果被归还的对象基于某些原因(有效性校验失败、资源卸载失败)被销毁了,那么需要检查确保是否需要创建空闲对象来满足对象池的要求。当时调用了ensureIdle(1, false)方法,接下来我们就针对ensureIdle方法来做详细的源码解析。

GenericObjectPool类中ensureIdle方法解析

/**
* Tries to ensure that {@code idleCount} idle instances exist in the pool.
* <p>
* Creates and adds idle instances until either {@link #getNumIdle()} reaches {@code idleCount}
* or the total number of objects (idle, checked out, or being created) reaches
* {@link #getMaxTotal()}. If {@code always} is false, no instances are created unless
* there are threads waiting to check out instances from the pool.
*
* @param idleCount the number of idle instances desired
* @param always true means create instances even if the pool has no threads waiting
* @throws Exception if the factory's makeObject throws
*
* 尝试确保对象池中有足够的空闲对象
* 创建新的然后添加到对象池中,直到空闲对象数量达到指定的idleCount。
* 当然前提也是对象池中所有对象的数量不能大于对象池的maxTotal配置。
* idleCount参数:指定要创建多少个空闲对象
* always参数:如果为true,意味着不会关注到底有没有调用方在等待获取对象。如果为false,意味着只有存在着等待获取对象的调用方的时候才会创建对象。

* ensureIdle(1, false) 就意思着:如果当前有调用方在排队等待获取空闲对象,那么我就创建1个空闲对象。
*/
private void ensureIdle(int idleCount, boolean always) throws Exception {
    // 如果idleCount小于1,或者对象池已经关闭,或者always为false但是没有处于等待的调用方。 这不会创建对象。
    if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
        return;
    }

    while (idleObjects.size() < idleCount) { // 如果空闲对象数量小于指定的idleCount
        PooledObject<T> p = create(); // 创建一个新对象,“关于create方法可以参见:addObject方法的解析”
        if (p == null) { // 如果p为空,说明创建失败,跳出循环
            // Can't create objects, no reason to think another call to
            // create will work. Give up.
            break;
        }
        if (getLifo()) { // 判断回收策略,然后决定是往队列的开头加还是末尾加
            idleObjects.addFirst(p);
        } else {
            idleObjects.addLast(p);
        }
    }
    if (isClosed()) { // 如果对象池已经处于关闭状态
        // Pool closed while object was being added to idle objects.
        // Make sure the returned object is destroyed rather than left
        // in the idle object pool (which would effectively be a leak)
        clear(); // 清理所有空闲对象,包括刚刚创建的对象,防止内存泄漏
    }
}     
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对上述代码,我们可以使用commons-pool2提供的对象池进行修改。具体实现步骤如下: 1. 引入commons-pool2的依赖包,例如在Maven项目中,在pom.xml文件中添加以下代码: ``` <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency> ``` 2. 创建一个连接池对象,用于管理HttpClients对象的创建和回收: ```java GenericObjectPool<CloseableHttpClient> httpClientPool = new GenericObjectPool<>(new HttpClientFactory()); ``` 其中,HttpClientFactory是一个实现了PooledObjectFactory接口的类,用于创建和销毁HttpClients对象。具体实现可以参考下面的代码: ```java public class HttpClientFactory implements PooledObjectFactory<CloseableHttpClient> { @Override public PooledObject<CloseableHttpClient> makeObject() throws Exception { return new DefaultPooledObject<>(HttpClients.createDefault()); } @Override public void destroyObject(PooledObject<CloseableHttpClient> pooledObject) throws Exception { CloseableHttpClient httpClient = pooledObject.getObject(); httpClient.close(); } @Override public boolean validateObject(PooledObject<CloseableHttpClient> pooledObject) { CloseableHttpClient httpClient = pooledObject.getObject(); return httpClient != null && httpClient.getConnectionManager().isShutdown(); } @Override public void activateObject(PooledObject<CloseableHttpClient> pooledObject) throws Exception { // do nothing } @Override public void passivateObject(PooledObject<CloseableHttpClient> pooledObject) throws Exception { // do nothing } } ``` 3. 使用连接池对象获取HttpClients对象,例如: ```java CloseableHttpClient httpClient = httpClientPool.borrowObject(); ``` 4. 使用完HttpClients对象后,将其归还给连接池: ```java httpClientPool.returnObject(httpClient); ``` 通过上述步骤,我们就可以使用commons-pool2提供的对象池来管理HttpClients对象的创建和回收了。这样可以大大减少对象的创建和销毁次数,提高程序的性能和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值