简单聊聊对象池及Apache Commons Pool的使用

1,472 阅读3分钟

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

背景

最近在看rocketmq-exporter源码,发现了rocketmq-exporter在管理rocketmq-client使用了一个apache的三方库commons-pool,所以打算研究下commons-pool(对象池化技术)

作用

commons-pool的使用场景是什么呢?很简单,就是一些对象的场景和销毁代价比较大的,就需要使用对象池化技术,也就是commons-pool。 类似我们的数据库连接池、线程池

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
  <version>2.11.1</version>
</dependency>

官网

核心组件

对象工厂

目前对象工厂有两个常用的抽象类

  • BaseKeyedPooledObjectFactory
  • BasePooledObjectFactory

BaseKeyedPooledObjectFactory管理的对象池是一个key value的样子 BasePooledObjectFactory管理的就是一个vlaue对象 举个最简单的例子,比如我们只有一个RocketMQ集群 我们使用BasePooledObjectFactory即可,如果我们有多个RocketMQ集群,不同集群获取的rocketmq-client是不同的,所以我们使用BaseKeyedPooledObjectFactory更合适

ObjectFactory主要是负责对象的创建、初始化、状态检测和消费,这些方法都需要我们自己去实现

这里我们使用BasePooledObjectFactory类给大家演示一个demo 要创建的对象

  • XiaoZou
public class XiaoZou {

    private final String name;

    public XiaoZou(String name) {
        this.name = name;
    }

    public void create() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 对象:" + name + "正在被创建。。。。。。");

    }

    public void destroy() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 对象:" + name + "正在被销毁。。。。。。");

    }

    public boolean isValid() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 对象" + name + "正在检验是否可用。。。。。。");
        return true;
    }

}

我们的对象工厂

  • XiaoZouBasePooledObjectFactory
public class XiaoZouBasePooledObjectFactory extends BasePooledObjectFactory<XiaoZou> {

    @Override
    public XiaoZou create() {
        // 创建一个新的MyObject对象
        XiaoZou myObject = new XiaoZou(UUID.randomUUID().toString());
        myObject.create();
        return myObject;
    }

    @Override
    public PooledObject<XiaoZou> wrap(XiaoZou myObject) {
        // 将MyObject对象封装到一个PooledObject对象中并返回
        return new DefaultPooledObject<>(myObject);
    }

    @Override
    public void destroyObject(PooledObject<XiaoZou> pooledObject) {
        // 销毁对象
        XiaoZou myObject = pooledObject.getObject();
        myObject.destroy();
    }

    @Override
    public boolean validateObject(PooledObject<XiaoZou> pooledObject) {
        // 验证对象是否可用
        XiaoZou myObject = pooledObject.getObject();
        return myObject.isValid();
    }

}

对象池

  • GenericObjectPool 我们最终操作获取对象都不是通过上面提到的PooledObjectFactory,而是通过GenericObjectPoolGenericObjectPool通过持有PooledObjectFactory然后操作对象

GenericObjectPool实现了ObjectPool接口,ObjectPool定义了操作对象的一些方法

public interface ObjectPool<T> extends Closeable {
    
    //添加对象
    void addObject() throws Exception, IllegalStateException,
            UnsupportedOperationException;
    
    // 批量添加对象
    default void addObjects(final int count) throws Exception {
        for (int i = 0; i < count; i++) {
            addObject();
        }
    }

    //从池中获取对象
    T borrowObject() throws Exception, NoSuchElementException,
            IllegalStateException;
            
    //清除池,池可用
    void clear() throws Exception, UnsupportedOperationException;

    //关闭池,池不可用
    @Override
    void close();

    //获取活跃对象个数
    int getNumActive();

    //获取空闲对象个数
    int getNumIdle();
    
    //废弃对象
    void invalidateObject(T obj) throws Exception;

    default void invalidateObject(final T obj, final DestroyMode destroyMode) throws Exception {
        invalidateObject(obj);
    }
    
    //将对象放回池中
    void returnObject(T obj) throws Exception;

}

对象池配置

关于对象池的配置我们都封在在GenericObjectPoolConfig中 这里我们可以看看GenericObjectPoolConfig的一个简单配置

// 创建对象池配置
GenericObjectPoolConfig<XiaoZou> poolConfig = new GenericObjectPoolConfig<>();
// 对象池中最大对象数
poolConfig.setMaxTotal(3);
// 对象池中最小空闲对象数
poolConfig.setMinIdle(1);
// 对象池中最大空闲对象数
poolConfig.setMaxIdle(2);
// 当对象池耗尽时,是否等待获取对象
poolConfig.setBlockWhenExhausted(true);
// 创建对象时是否进行对象有效性检查
poolConfig.setTestOnCreate(true);
// 借出对象时是否进行对象有效性检查
poolConfig.setTestOnBorrow(true);
// 归还对象时是否进行对象有效性检查
poolConfig.setTestOnReturn(true);
// 空闲时是否进行对象有效性检查
poolConfig.setTestWhileIdle(true);
// 获取对象最大等待时间 默认 -1 一直等待
poolConfig.setMaxWait(Duration.ofSeconds(2));

其实可以看到和线程池的一些属性有些类似,毕竟都是池

完整demo演示

博客涉及的源码已全部上传至github
地址: github.com/weihubeats/…

定义一个我们要管理的对象

  • XiaoZou
public class XiaoZou {

    private final String name;

    public XiaoZou(String name) {
        this.name = name;
    }

    public void create() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 对象:" + name + "正在被创建。。。。。。");

    }

    public void destroy() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 对象:" + name + "正在被销毁。。。。。。");

    }

    public boolean isValid() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 对象" + name + "正在检验是否可用。。。。。。");
        return true;
    }

}
  • 定义对象工厂
public class XiaoZouBasePooledObjectFactory extends BasePooledObjectFactory<XiaoZou> {

    @Override
    public XiaoZou create() {
        // 创建一个新的MyObject对象
        XiaoZou myObject = new XiaoZou(UUID.randomUUID().toString());
        myObject.create();
        return myObject;
    }

    @Override
    public PooledObject<XiaoZou> wrap(XiaoZou myObject) {
        // 将MyObject对象封装到一个PooledObject对象中并返回
        return new DefaultPooledObject<>(myObject);
    }

    @Override
    public void destroyObject(PooledObject<XiaoZou> pooledObject) {
        // 销毁对象
        XiaoZou myObject = pooledObject.getObject();
        myObject.destroy();
    }

    @Override
    public boolean validateObject(PooledObject<XiaoZou> pooledObject) {
        // 验证对象是否可用
        XiaoZou myObject = pooledObject.getObject();
        return myObject.isValid();
    }

}
  • 创建对象池
public enum XiaZouPool {

    /**
     * 线程安全的单例
     */
    INSTANCE;

    private final GenericObjectPool<XiaoZou> objectPool;

    XiaZouPool() {
        // 创建对象池配置
        GenericObjectPoolConfig<XiaoZou> poolConfig = new GenericObjectPoolConfig<>();
        // 对象池中最大对象数
        poolConfig.setMaxTotal(3);
        // 对象池中最小空闲对象数
        poolConfig.setMinIdle(1);
        // 对象池中最大空闲对象数
        poolConfig.setMaxIdle(2);
        // 当对象池耗尽时,是否等待获取对象
        poolConfig.setBlockWhenExhausted(true);
        // 创建对象时是否进行对象有效性检查
        poolConfig.setTestOnCreate(true);
        // 借出对象时是否进行对象有效性检查
        poolConfig.setTestOnBorrow(true);
        // 归还对象时是否进行对象有效性检查
        poolConfig.setTestOnReturn(true);
        // 空闲时是否进行对象有效性检查
        poolConfig.setTestWhileIdle(true);
        // 获取对象最大等待时间 默认 -1 一直等待
        poolConfig.setMaxWait(Duration.ofSeconds(2));
        // 创建对象工厂
        XiaoZouBasePooledObjectFactory objectFactory = new XiaoZouBasePooledObjectFactory();
        // 创建对象池
        objectPool = new GenericObjectPool<>(objectFactory, poolConfig);
    }

    /**
     * 从对象池中借出一个对象
     * @return
     * @throws Exception
     */
    public XiaoZou borrowObject() throws Exception {
        XiaoZou zou = objectPool.borrowObject();
        int numActive = objectPool.getNumActive();
        int numIdle = objectPool.getNumIdle();
        System.out.println("ThreadName:" + Thread.currentThread().getName() + " 活跃对象数量:" + numActive + " 空闲对象数量:" + numIdle);
        System.out.println("------------------------------------------------------------");
        return zou;
    }

    public void returnObject(XiaoZou myObject) {
        // 将对象归还给对象池
        objectPool.returnObject(myObject);
    }

    /**
     * 获取活跃的对象数
     * @return
     */
    public int getNumActive() {
        return objectPool.getNumActive();
    }

    /**
     * 获取空闲的对象数
     * @return
     */
    public int getNumIdle()   {
        return objectPool.getNumIdle();
    }

}
  • 测试
@Test
public void singleTest() throws Exception{
    // 最大对象数量3 最小空闲对象数量1 最大空闲数量 2
    XiaZouPool myObjectPool = XiaZouPool.INSTANCE;
    numActiveAndNumIdle(myObjectPool);
    // 获取对象
    XiaoZou obj = myObjectPool.borrowObject();
    // 获取对象
    XiaoZou obj2 = myObjectPool.borrowObject();
    // 获取对象
    XiaoZou obj3 = myObjectPool.borrowObject();
    
    // 获取对象 已超出最大对象数
    XiaoZou obj4 = myObjectPool.borrowObject();
    
    //归还对象
    myObjectPool.returnObject(obj);
    System.out.println("ThreadName:" + Thread.currentThread().getName() + " returned: " + JSONObject.toJSONString(obj));
    sleep();
    numActiveAndNumIdle(myObjectPool);
}

这里故意获取对象超过最大对象数量演示超时

总结

本文我们主要学习了Apache Commons Pool是什么,以及核心组件,还有如何使用,不涉及原理和源码的分析。在知道了如何使用后我们后面才会去分析源码