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

为什么会有对象池

在实际的应用工程当中,存在一些被频繁使用的、创建或者销毁比较耗时、持有的资源也比较昂贵的一些对象。比如:数据库连接对象、线程对象。所以如果能够通过一种方式,把这类对象统一管理,让这类对象可以被循环利用的话,就可以减少很多系统开销(内存、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、returnObject方法的实现,对应链接地址:

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

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

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

ObjectPool接口中invalidateObject解析

/**
* Invalidates an object from the pool.
* <p>
* 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.
* <p>
* This method should be used when an object that has been borrowed is
* determined (due to an exception or other problem) to be invalid.
*
* @param obj a {@link #borrowObject borrowed} instance to be disposed.
*
* @throws Exception if the instance cannot be invalidated
*/
/**
 * 销毁(置为无效)池子中的一个对象(从池子中销毁一个对象)
 * 按照约定,obj,也就是要归还的对象,一定是通过borrowObject方法、
 * 或者是实现类或者子接口中的和borrowObject有一定逻辑关系的方法获得的(返回的)的对象。
 * 这个invalidateObject方法应该什么时候调用呢?当要把一个借走的对象置为无效的时候。(可能是因为对象的调用发生了异常或者其他未知原因)
 *
 * 入参 obj,就是要置为无效的对象。
 * 如果置为无效发生异常,抛出Exception。
 */
void invalidateObject(T obj) throws Exception;

接口层面的注释,没什么可多讲的,我们重点看一下GenericObjectPool对于invalidateObject的具体实现。

GenericObjectPool类中invalidateObject解析

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;

    /*
     * createCount 用来统计对象池当前一共创建了多少对象。这个值在多线程并发调用create方法时可能会超过maxActive的值的。
     * 但是create方法会有相应的防护机制,能够确保对象数量绝对不够超过maxActive。(这个我们在create方法解析中会看到是如何处理的)
     * 他的作用就是防止对象池中的对象数量超过最大限制。
     *
     * 关于createCount的用处可参考
     * addObject方法解析:https://blog.csdn.net/weixin_42340670/article/details/107749058
     */
    private final AtomicLong createCount = new AtomicLong(0);


    /**
     * {@inheritDoc}
     * <p>
     * Activation of this method decrements the active count and attempts to
     * destroy the instance.
     *
     * @throws Exception             if an exception occurs destroying the
     *                               object
     * @throws IllegalStateException if obj does not belong to this pool
     */
    /**
     * 触发这个方法的话,将会减少活跃对象数量,并尝试销毁指定的对象。
     *
     * 销毁实例(对象)的时候可能会产生Exception
     * 如果传入的对象不属于当前对象池,则抛出IllegalStateException异常。
     */ 
    @Override
    public void invalidateObject(T obj) throws Exception {
        // 对象池allObjects是一个Map,他需要一个IdentityWrapper类型的key来获取出包装了原始对象的PooledObject对象。
        // IdentityWrapper的核心作用,是为了消除原始对象(业务对象)T的可能重写的hashcode和equals方法带来的影响。
        PooledObject<T> p = allObjects.get(new IdentityWrapper<T>(obj));
        // 如果p为空,说明在池中没有找到obj,有两种可能:
        // 1、传入的对象是一个外部对象,从来没有在池子中存在过。
        // 2、传入的对象曾经在池子中,但是可能被其他策略回收掉了。
        if (p == null) { 
            // 这里指的是,如果配置了遗弃对象管理策略,可能传入对象就是被遗弃管理策略回收掉了。(也只是可能)
            if (isAbandonedConfig()) {  
                return; // 正常终止
            } else { // 如果没配置遗弃对象管理策略,还没在池子中找到传入的对象,那就说明传入的对象从来没有属于过池子。
                throw new IllegalStateException(
                        "Invalidated object not currently part of this pool");// 抛出异常
            }
        }
        synchronized (p) { // 如果正常获取到了P
            // 如果p的状态不是INVALID,那么就调用destroy方法销毁p。
            // 这个状态判断,主要是为了防止重复销毁。
            if (p.getState() != PooledObjectState.INVALID) {
                destroy(p); // 销毁对象,下面有该方法的解析
            }
        }
        // 销毁了一个对象后,是否还需要补充进来一个空闲对象呢? 如果当前正存在着调用方正在等待获取空闲对象,那么就需要创建一个空闲对象。
        ensureIdle(1, false); // 这个方法下面会单独解析
    }

    /**
     * Destroys a wrapped pooled object.
     *
     * @param toDestory The wrapped pooled object to destroy
     *
     * @throws Exception If the factory fails to destroy the pooled object
     *                   cleanly
     */
    /**
     * 销毁一个包装的池对象
     * @param toDestory 要销毁的对象
     * @throws Exception 如果调用工厂的销毁方法失败可能会抛出Exception异常
     */
    private void destroy(PooledObject<T> toDestory) throws Exception {
        // 首先就是变更状态,无论当前是什么状态,调用invalidate方法后,状态就变为INVALID
        // 这里也就呼应上了,上面的代码中为什么要在调用destroy方法之前,判断一下状态不是INVALID,就是为了防止重复销毁。
        toDestory.invalidate(); 
        // 从空闲队列中移除该对象(有可能该对象根本就不属于空闲队列,不够remove一个不存在与列表中的元素,对于列表也没有任何影响)
        idleObjects.remove(toDestory);
        // 从池子中移除该对象
        allObjects.remove(new IdentityWrapper<T>(toDestory.getObject()));
        try {
            factory.destroyObject(toDestory); // 调用对象工厂的destroyObject方法来释放资源
        } finally { // 无论destroyObject执行成功与否
            // destroyedCount是为了统计对象池,一共执行了多少销毁动作。这个数值是有可能大于对象池的最大容量的。
            destroyedCount.incrementAndGet(); 

            // createCount减一,保证该数值和allObjects的size一致。
            // 问题:如果需要获取对象池的对象数量,直接用allObjects.size()就可以了,那为什么还要搞个createCount呢?
            // 就像在前面属性解析中提到的,createCount的重要作用是为了限制对象的数量不超过池子设定的最大限制。
            // 对象添加的时候,要提前校验是否超出限制,可能存在并发情况,基于allObjects.size()来做判定,会有并发问题。
            createCount.decrementAndGet();
        }
    }

}

在上面的方法解析中,销毁了一个对象之后,还需要检查确保是否需要创建空闲对象来满足对象池的要求。当时调用了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(); // 清理所有空闲对象,包括刚刚创建的对象,防止内存泄漏
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,针对上述代码,我们可以使用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对象的创建和回收了。这样可以大大减少对象的创建和销毁次数,提高程序的性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值