common-pool对象池学习

  张一帆   2021年05月13日


问如果你要设计一个池化对象,你会怎么做?

  设计一个T类型的池子,把T类型的资源都放到池子中。资源负责资源的事情,池子负责管理资源。管理池子要做到资源数量的管理,资源状态的管理,资源创建和销毁,线程安全,资源记录。还要考虑到复用的可能。

  让我们看看common-pool2的组成部分吧:

  • ObjectPool: 存取和状态管理的实现。我们直接操作的线程池就是在这里定义的。这里定义的是如何获取和释放对象等操作。至于具体创建对象的代码,是由PooledObjectFactory来负责的。
  • PooledObject: 将真正的资源进行封装,封装的过程中添加一些附加信息,比如状态信息,时间等信息。这些信息方便再操作连接资源时完成特定的操作。
  • PooledObjectFactory:创建,初始化,销毁对象的具体实现的地方。

池化技术

//一个简单的池结果。它继承了Closeable接口,也就是说是可以关闭的数据源或目标。T是实际存在于池中的类型。
public interface ObjectPool<T> extends Closeable{
    
    //通过PooledObjectFactory创建对象或者通过其他机制放入空闲池对象。addObject也用来预热空闲池。
    void addObject() throws Exception, IllegalStateException,
            UnsupportedOperationException;
            
    //传入参数count,调用count次addObject。 方法的类型是default,也就是说这个方法直接实现方法体,不用实现者实现这个方法。
    default void addObjects(final int count) throws Exception {
        for (int i = 0; i < count; i++) {
            addObject();
        }
    }
    
    /*
    从对象池中获取一个实例。
    创建的实例要不是通过PooledObjectFactory中makeObject()新建出来的,要不就是用了前一个已经被PooledObjectFactory中activateObject()和PooledObjectFactory中validateObject()的空闲对象。
    activateObject()和validateObject()这两个方法需要在不同的池类型文件中自己去实现。
    */
    T borrowObject() throws Exception, NoSuchElementException,
            IllegalStateException;
    
    //清除随机对象放回到池中作为空闲对象。释放这个对象的相关引用资源。在clear之前一定是经过PooledObjectFacotry的destroyObject()过。
     void clear() throws Exception, UnsupportedOperationException;
     
    //继承自Closeable接口,负责关闭并释放有关联系的系统资源。如果之前已经关闭了, 则调用此方法无效。
    @Override
    void close();
    
    //返回当前有多少实例从池对象中被借出了。如果返回的是负数代表这个信息不可用。
    int getNumActive();
    
    //返回当前当前池中有多少空闲对象。这个值是一个近似值,因为他只代表还没有创建成新资源的被借走的对象。我理解的是还没有被分配内存地址(allocate)。
    int getNumIdle();
    
    /*
    使池中对象失效
    按照约定,参数obj必已经被borrwoObject()或者被一个定义在子实现类中定义的方法实现了获取逻辑的对象。如果失败了就返回异常
    */
     void invalidateObject(T obj) throws Exception;
     
     //比上面多了一个DestoryMode。DestoryMode是个枚举,分别为普通摧毁和被禁的摧毁。最终调用的是PooledObjectFactory的destoryObject()。但不知为啥,我发现其实mode参数最终没有用上……。往下看,看到工厂的destoryObject()
     default void invalidateObject(final T obj, final DestroyMode mode) throws Exception {
        invalidateObject(obj);
    }
    
    /*
    返回一个池对象中的实例。同invalidateObject()
    异常中会抛出IllegalStateException。原因是如果有任何(把除了已经被分配内存的有状态的资源,即被借出了的。或者返回一个对象超过一次以上或者返回一个从未被借出过的对象)操作就会触发这个异常
    */
     void returnObject(T obj) throws Exception;
}
/*
用来定义池对象的一个wrapper 接口,用于跟踪对象的附加信息,比如状态、创建时间、使用时间等。这个类的实现必须是线程安全的。
*/
public interface PooledObject<T> extends Comparable<PooledObject<T>> {

    //返回被包装的实际对象
    T getObject();

    //返回该对象的创建时间
    long getCreateTime();
    
    //以毫秒为单位获得该对象最后在活动状态中使用的时间(它可能仍然处于活动状态,在这种情况下后续调用将返回一个增加的值)。
    long getActiveTimeMillis();

    //借出的次数
    default long getBorrowedCount() {
        return -1;
    }

    //空闲时间
    long getIdleTimeMillis();

    //上次借用时间
    long getLastBorrowTime();

    //上次归还时间
    long getLastReturnTime();

     /**
     * 返回上次使用时间的一个估计值,如果Pooled Object实现了TrackedUse接口
     * 那么返回值将是TrackedUse.getLastUsed()和getLastBorrowTime()的较大者,
     * 否则返回值和getLastBorrowTime()相等
     */
    long getLastUsedTime();

     /*
     因为接口继承了Comparable接口。所以这个接口需要实现自己的排序逻辑。
     默认是按照空闲时间排序的。
     */
    @Override
    int compareTo(PooledObject<T> other);

    @Override
    boolean equals(Object obj);

    @Override
    int hashCode();

    @Override
    String toString();

    //尝试将池对象置于PooledObjectState.EVICTION状态,即收回
    boolean startEvictionTest();

     //是否完成了收回测试
    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);

    //分配内存,这个比较重要。因为连接池中的资源被分配后才能算真正的解除。分配内存的过程中可能会出现抢占资源的情况,所以会发生异常。反正,最终返回ture/false来代表是否分配内存成功,即完成了borrow,状态改为PooledObjectState.ALLOCATED
    boolean allocate();

    //与上面一个方法作用相反,将PooledObjectState.ALLOCATED置为PooledObjectState.IDLE
    boolean deallocate();

    //将对象置为PooledObjectState.INVALID无效状态
    void invalidate();

    //设置是否记录对象使用的堆栈信息,可用于池泄漏时问题追溯
    void setLogAbandoned(boolean logAbandoned);

     /*
     设置记录堆栈信息时的策略,是全量还是只记泄露的信息。
     设置为false时,泄露信息只包括调用类的信息而不是方法名。
     设置为数字时,制使用包含完整堆栈跟踪信息的堆栈遍历机制;否则,如果可能,使用更快的实现。
     参数是自common-pool2-2.7.0开始有的。
     */
    default void setRequireFullStackTrace(final boolean requireFullStackTrace) {
        // noop
    }

     //记录当前对象最后一次使用的堆栈信息
    void use();

    //打印对象的调用堆栈信息
    void printStackTrace(PrintWriter writer);

    //返回对象目前的状态
    PooledObjectState getState();

    //标记该对象发生了泄漏
    void markAbandoned();

    //标记该对象正在被归还到对象池
    void markReturning();
}
/*
这个接口主要提供给ObjectPool接口使用。因为这个接口定义了池中对象的生命周期相关的所有方法。
按照约定,以下情况ObjectPool会代理到PooledObjectFactory中:
1.不论何时创建一个实例调用makeObject()时
2.当实例在借出之前,已经处于passivated,每次实例在调用activateObject时
3.当一个实例被激活时。当一个实例已经返回到池里并且正在做检查,又在passivated(卸载)之前时。当acitvated实例且确定能被借出时
4.每个实例返回到连接池后,在调用passivateObject()时
5.当实例正在从池中销毁时,destoryObject()调用时

这个工厂必须确保线程安全,工厂只需要ObjectPool保证操作的实际类型和工厂一致即可。
*/
public interface PooledObjectFactory<T> {
  
   //创建一个资源实例,这个资源实例可以被封装起来。
  PooledObject<T> makeObject() throws Exception;

   /*
   销毁一个不再被池子需要的实例。用的销毁模式是normal。
   实现了这个方法的类需要注意的是:方法内没有任何保证资源处于什么状态和实现的方法如何去处理非预期的errors
   所以,实现者需要考虑到对象没有被摧毁,即被垃圾收集器忘掉了,从而永远储存在了内存中。
   */
  void destroyObject(PooledObject<T> p) throws Exception;

   //同上,只是采用了DestoryMode模式。但是其实感觉是mode根本没用用上。。。
  default void destroyObject(final PooledObject<T> p, final DestroyMode mode) throws Exception {
      destroyObject(p);
  }

  //确保实例能够安全的返回到池子里
  boolean validateObject(PooledObject<T> p);

   //重新初始化池返回的实例
  void activateObject(PooledObject<T> p) throws Exception;

   //卸载要返回到空闲对象池的实例
  void passivateObject(PooledObject<T> p) throws Exception;
}

  至此,我分析完了三个接口。三个接口各司其职,互相协助。我用一张我画的图片来描述的话,一个管对象的打包,一个管对象的创建,一个管对象的进出口。用jedis举例的话就是,jedis资源打包成封装格式,封装格式被工厂创建,创建成真正使用的资源。这些资源再被借出或者归还。多么的形象哈哈。

我画的

  下面这张图是这三个接口相关的关系。 我画的

  另外,上面的接口都用到了对象的状态,对象状态如下:

public enum PooledObjectState {
    /**
     * 在队列中,未被使用,空闲状态。调用allocate()方法后对象应该被置于该状态
     */
    IDLE,

    /**
     * 已分配,在使用中,调用allocate()方法后应该讲对象置于该状态
     */
    ALLOCATED,

    /**
     * 在队列中, 当前正在测试,可能会被回收。在此状态被借出后状态回被置为EVICTION_RETURN_TO_HEAD
     */
    EVICTION,

    /**
     * 不在队列中。当借用该对象时发现对象,发现正在进行回收测试,故将EVICTION更
     * 改EVICTION_RETURN_TO_HEAD,表明曾经在回收过程中被借出,在回收完后它应该从新添加到队列的头部。
     */
    EVICTION_RETURN_TO_HEAD,

    /**
     * 在队列中,目前正在进行校验
     */
    VALIDATION,

    /**
     * 不在队列中,当前正在验证。当对象从池中被借出,
     * 在配置了testOnBorrow的情况下,对像从队列移除和进行预分配的时候会进行验证(借用时校验)
     */
    VALIDATION_PREALLOCATED,

   /**
     * 不在队列中,正在进行验证。从池中借出对象时,发现对象正在进行校验,并将对象状态改为该状态
     */
    VALIDATION_RETURN_TO_HEAD,

    /**
     *无效的,并且将要或已经被销毁。
     */
    INVALID,

    /**
     * 泄漏的
     */
    ABANDONED,

    /**
     *归还中,调用markReturning()方法会将对象状态改为此状态,表明正在归还一个对象
     */
    RETURNING
}

参考链接: