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

为什么会有对象池

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

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

ObjectPool接口中addObject解析

/**
* Create an object using the {@link PooledObjectFactory factory} or other
* implementation dependent mechanism, passivate it, and then place it in
* the idle object pool. <code>addObject</code> is useful for "pre-loading"
* a pool with idle objects. (Optional operation).
* 
* PooledObjectFactory是框架提供的一个对象工厂接口,他定了一些池中对象的特定操作方法。
* 如果要创建一个对象,可以实现PooledObjectFactory接口,按照common pool的约定来生产一个对象。
* 这只是common pool建议的实现方式。但创建对象不一定非要搞一个对象工厂出来,你也完全按自己意愿用任何形式实现。
* 只要你最终把对象放到池子里就可以,因为这才是addObject的核心要做的事情。
* 当然了文档里重点提到了idle object pool(空闲对象池)。意思是这个新对象要放到空闲对象池里。
*   个人觉得这其实已经是对实现者的一种干扰了,实现者是否需要把空闲对象、活跃对象分开存储那是实现者的事情。
* 这个addObject往往是在在给一个对象池预加载对象的时使用。
* 
* @throws Exception
*              when {@link PooledObjectFactory#makeObject} fails.
* 意思是,在这个方法的实现中,如果对象创建失败,抛出Exception异常(这里也是默认的和PooledObjectFactory绑定了,认为生产对象一定是通过PooledObjectFactory)
* @throws IllegalStateException
*              after {@link #close} has been called on this pool.
* 意思是,在这个方法的实现中,如果发现已经调用了close方法关闭了对象池,那么应该抛出IllegalStateException异常。
* @throws UnsupportedOperationException
*              when this pool cannot add new idle objects.
* 意思是,在这个方法的实现中,当对象池无法再添加新的空闲对象时,应该抛出UnsupportedOperationException异常。
*/
void addObject() throws Exception, IllegalStateException,
        UnsupportedOperationException;

上面我们对addObject的接口定义进行一些解读,那么其中有一个点值得重点关注,就是PooledObjectFactory,这是一个对象工厂(我们先不对其进行具体的分析),ObjectPool接口是非常倾向、或者说如果作为common pool框架的开发者,那么就应该通过实现PooledObjectFactory的makeObject来进行对象的创建。那么我们接下就看看,ObjectPool的实现类GenericObjectPool是如何实现addObject方法的。

GenericObjectPool中addObject解析

为了便于阅读,只截取了和我们讨论分析相关的重点代码,并且做了一些顺序的调整。我们先对一些必要属性进行说明和解析,然后再看具体方法的逻辑。

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这两个类(接口)的作用,我们下面结合代码会再详细讲。
     */ 
    private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects =
        new ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>();
    /*
     * The combined count of the currently created objects and those in the
     * process of being created. Under load, it may exceed {@link #_maxActive}
     * if multiple threads try and create a new object at the same time but
     * {@link #create()} will ensure that there are never more than
     * {@link #_maxActive} objects created at any one time.
     */
    /*
     * createCount 用来统计对象池当前一共创建了多少对象。这个值在多线程并发调用create方法时可能会超过maxActive的值的。
     * 但是create方法会有相应的防护机制,能够确保对象数量绝对不够超过maxActive。(这个我们在create方法解析中会看到是如何处理的)
     * 他的作用就是防止对象池中的对象数量超过最大限制。
     */
    private final AtomicLong createCount = new AtomicLong(0);

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

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


    /**
     * Create an object, and place it into the pool. addObject() is useful for
     * "pre-loading" a pool with idle objects.
     * <p>
     * If there is no capacity available to add to the pool, this is a no-op
     * (no exception, no impact to the pool). </p>
     */
    /**
     * 创建一个对象,放到对象池里。在对象池初始化预加载空闲对象时调用
     * 如果对象池里放不下了,那么该方法不会抛出异常,也不会对对象池有任何影响。
     */ 
    @Override
    public void addObject() throws Exception {
        assertOpen(); // 校验连接池是否是open状态,实际上就是看看是不是非close状态,如果关闭了就不允许再添加。
        if (factory == null) { // 如果对象工厂为空,抛出异常。
            throw new IllegalStateException(
                    "Cannot add objects without a factory.");
        }
        // 此处调用了create方法来创建一个新的对象,所以这个create方法是一个比较核心的方法
        PooledObject<T> p = create(); 
        addIdleObject(p); // 把这个新建好的对象,放到空闲对象队列中。
    }

    /**
     * Attempts to create a new wrapped pooled object.
     * <p>
     * If there are {@link #getMaxTotal()} objects already in circulation
     * or in process of being created, this method returns null.
     *
     * @return The new wrapped pooled object
     *
     * @throws Exception if the object factory's {@code makeObject} fails
     */
    /**
     * 尝试创建一个包装对象(最原始的对象应该是:数据库连接、线程对象等),
     * 此处的PooledObject<T>中的T指的就是我们的原始对象类型,我们的原始对象也被包装在了PooledObject对象中。
     * 
     * 如果对象池中对象已经满了,或者正在创建中,这个方法返回null
     *
     * 如果factory的makeObject方法发生异常,则抛出
     */     
    private PooledObject<T> create() throws Exception {
        int localMaxTotal = getMaxTotal(); // 获取对象池的对象数量限制
        long newCreateCount = createCount.incrementAndGet();// createCount先自增
        /*
         maxTotal的默认值是-1,意味着不限制对象池中的对象数量,如果设置了一个大于-1的值,意味着配置了限制
         如果newCreateCount已经大于了maxTotal,或者大于了Integer的最大值,
         那么认为对象数量已经超出了限制,则返回null,同时createCount自减来保证自己存储的值和对象的数量是对等的。
        */
        if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||
                newCreateCount > Integer.MAX_VALUE) {
            createCount.decrementAndGet();
            return null;
        }

        // PooledObject是一个接口,common-pool为其提供了一个默认实现类DefaultPooledObject,可以满足绝大多数需求
        // 但是使用方也是完全可以自己实现PooledObject接口,在自己的factory中使用自己的PooledObject接口实现类。
        final PooledObject<T> p; 
        try {
            /*
             调用PooledObjectFactory接口的某个实现类的实例factory来创建一个对象。
             如果我们要想用GenericObjectPool来实现我们自己应用上的一个对象池,那么就必须提供一个PooledObjectFactory的实现类,
             并注入到GenericObjectPool的实例中。
             例如:Jedis使用GenericObjectPool实现自己的redis连接池,他对于PooledObjectFactory的实现类是redis.clients.jedis.JedisFactory
            */
            p = factory.makeObject(); // 调用factory的makeObject返回一个PooledObject对象实例,这个对象里面包装了我们需要的真正的原始对象。
        } catch (Exception e) {
            createCount.decrementAndGet(); // 如果产生异常,计数自减。
            throw e; // 向外抛出(这就对应上了方法注释上的描述)
        }

        // abandonedConfig是针对遗弃对象管理相关的配置
        AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getLogAbandoned()) {  // 如果配置了abandonedConfig
            p.setLogAbandoned(true); // 调用PooledObject对象的setLogAbandoned方法,可以针对这个对象进行使用情况追踪。
        }
        // 关于AbandonedConfig,请单独参考我的另外一篇解析:https://blog.csdn.net/weixin_42340670/article/details/107136994

        /*
         注意这个变量是createdCount,不是createCount。

         createCount
            是定义在本类内部,是本类的一个实例变量,
            用来统计对象池中有多少对象,
            在destory方法中会调用createCount.decrementAndGet().
         createdCount
            定义在父类BaseGenericObjectPool类中,
            他的作用是统计对象池一共创建多多少对象,
            没有任何地方会调用createdCount.decrementAndGet(),
            所以他是只增不减的。
        */
        createdCount.incrementAndGet(); // 只要成功的创建一个对象,那么createdCount就加1,即便对象被回收了,那么也不会影响createdCount。
        /*
         新的对象创建好了,需要把他添加到池子里。在GenericObjectPool中这个池子的容器allObjects用的一个ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>结构。
         key之所以用IdentityWrapper把原始对象包装起来,主要是因为原始对象是和使用方强耦合的业务对象,而且,封装了原始对象的PooledObject的实现类也有可能是使用方自定义实现的。
         既然原始对象类、PooledObject的实现类都(可能)是外部输入类,那么就有可能重写了hashcode和equals方法。直接放到Map里,就可能产生覆盖。
         所以key用IdentityWrapper包装了一下,IdentityWrapper内部还是基于原始对象T的内存地址作为算子来重新生成hashcode,保证了即使两个不同的T对象的hashcode相同,在容器中也肯定不会产生覆盖。
         同时因为IdentityWrapper的hashcode的算子完全基于T实例的内存地址生成,那么随便new一个IdentityWrapper的实例,只要你包装了T1,都可以作为key获取到包装了T1的PoolObject<T>。
        */
        allObjects.put(new IdentityWrapper<T>(p.getObject()), p); 
        return p; 
    }

    /**
     * Add the provided wrapped pooled object to the set of idle objects for
     * this pool. The object must already be part of the pool.  If {@code p}
     * is null, this is a no-op (no exception, but no impact on the pool).
     *
     * @param p The object to make idle
     *
     * @throws Exception If the factory fails to passivate the object
     */
    /**
     * 把一个PooledObject的实例对象添加到空闲对象集合中。
     * 要添加的对象一定是保证已经在对象池(allObjects)里了。如果p为null,那么不会pa
     * 如果p为null,那么不会抛出异常,也不会对对象池产生任何影响
     *
     * 如果产生异常的话,那就应该是passivateObject方法执行失败
     */     
    private void addIdleObject(PooledObject<T> p) throws Exception {
        if (p != null) {
            /*
             passivateObject方法按照PooledObjectFactory接口中对其职责的描述应该叫做:对一个对象进行反初始化,什么意思?
             抛开对象的创建流程先不谈,一个对象变为空闲,有可能是曾经被使用过,但是长时间没有被再使用,这个时候他本身除了持有很多核心资源外,可能还挂载了其他一些附加轻量的资源或者属性。
             对象池的目的就是为了避免重复的创建这种持有昂贵资源的对象,所以即使是对象状态变为空闲,但是本身的核心资源也不能被释放。
             但是那些外围的轻量的资源和属性是可以被卸载和重置的。所以可以这么理解,passivateObject方法是在对象变为空闲时,重置或者卸载那些非核心的资源和属性的。
             那怎么区分核心和非核心呢? 这个事情是使用方要操心的事情,你如果觉得你的对象持有的都是核心资源,passivateObject你还必须实现,所以你可以再方法内部啥逻辑都不写。其实JedisFactory就是这么干的。

             和passivateObject对应的是activateObject方法,activateObject意思是一个对象在被调用方使用前重新初始化一些附加信息。
             JedisFactory对于activateObject提供了实现,内部逻辑做了redis dbindex的修正动作。
            */
            factory.passivateObject(p); 
            if (getLifo()) { // 获取对象池定义的优先级策略
                idleObjects.addFirst(p); // 如果是后进先出,那么就把对象添加到队列的第一个位置。
            } else {
                idleObjects.addLast(p); // 如果是先进先出,那么就把对象添加到队列的最后一个位置。
            }
        }
    }
}    

通过上面的解析,我们能够知道GenericObjectPool定义好了往对象池添加对象的核心流程。而对象从哪里来,如何产生的,是需要使用方去实现PooledObjectFactory接口定义自己的对象工厂来实现的。所以任何一个基于GenericObjectPool来实现自己对象池的框架、或者应用程序最核心的就是编写自己的PooledObjectFactory实现类。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值