apache common pool2 对象池使用和原理

ApacheCommonsPool2是一个用于管理Java资源对象的对象池工具,它可以缓存对象,减少初次创建和后续重用的成本。文章通过一个具体的示例展示了如何创建自定义的资源类、键类、工厂类以及配置对象池,并提供了测试代码来说明对象的借用、归还、激活、钝化和销毁过程。此外,还深入到`borrowObject`方法的源码,解释了对象池在资源不足时的处理策略,包括等待、新建对象、销毁旧对象等机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

apache common pool2 对象池使用和原理

简介

apache common pool2 对象池是管理管理 Java 资源对象的工具,主要功能是可以将对象缓存起来,当首次使用时创建,用完返回,下次使用时可以直接取用而不必重新创建。可以用于数据库连接,Socket 等资源的管理中。

使用示例

引入 maven 依赖

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

测试代码

MyObject :自定义的资源类
public class MyObject {
    LocalTime time;
    public String id;
    int seq = 0;
    
    public MyObject(String id){
        this.id = id;
        time = LocalTime.now();
    }
    
    public void cry(){
        System.out.println(time + "cry "+ seq++);
        this.hashCode();
    }
    
    public boolean isOk(){
        return this.seq  < 2;
    }
}
MyKey :自定义的资源对象的 key,用于区分资源类
public class MyKey {
    public String key ;
    public MyKey(String key){
        this.key = key;
    }
    @Override
    public int hashCode(){
        return (this.key).hashCode();
    }
}
MyPooledObjectFactory : 自定义 Factory 类,实现了 apache 的接口,用于管理资源类
public class MyPooledObjectFactory implements KeyedPooledObjectFactory<MyKey ,MyObject> {
    @Override
    public void activateObject(MyKey s, PooledObject<MyObject> pooledObject) throws Exception {
        System.out.println("激活对象,借用时调用");
    }
    @Override
    public void destroyObject(MyKey s, PooledObject<MyObject> pooledObject) throws Exception {
        System.out.println("销毁对象");
    }
    @Override
    public PooledObject<MyObject> makeObject(MyKey s) throws Exception {
        System.out.println("创建对象");
        MyObject object = new MyObject(s.key);
        return new DefaultPooledObject(object);
    }
    @Override
    public void passivateObject(MyKey s, PooledObject<MyObject> pooledObject) throws Exception {
        System.out.println("钝化对象,归还时调用");
    }
    @Override
    public boolean validateObject(MyKey s, PooledObject<MyObject> pooledObject) {
        System.out.println("判断对象是否可用");
        return pooledObject.getObject().isOk();
    }
}
MyObjectPool : 封装一个对象池工具并配置对象池
public class MyObjectPool {
    @Getter
    private final GenericKeyedObjectPool<MyKey,MyObject> pool;
    public MyObjectPool() {
        GenericKeyedObjectPoolConfig<MyObject> config = new GenericKeyedObjectPoolConfig<>();
        // 最大资源数
        config.setMaxTotal(1);
        // 每个 key 对应的最大资源数
        config.setMaxTotalPerKey(1);
        // 最大 key 数量
        config.setMaxIdlePerKey(1);
        // 没有可用资源时是否要等待
        config.setBlockWhenExhausted(true);
        // 创建新资源对象后是否立即校验是否可用
        config.setTestOnCreate(false);
        // 借用资源对象前是否校验是否可用
        config.setTestOnBorrow(true);
        KeyedPooledObjectFactory<MyKey , MyObject> factory = new MyPooledObjectFactory();
        this.pool = new GenericKeyedObjectPool<>(factory, config);
    }
}
测试
    public static void main(String[] args) throws Exception {
        MyObjectPool myObjectPool = new MyObjectPool();
        GenericKeyedObjectPool<MyKey , MyObject> pool  = myObjectPool.getPool();
        MyKey key = new MyKey("1");
        for(int i=0;i<3;i++){
            MyObject myObject = pool.borrowObject(key);
            myObject.cry();
            pool.returnObject(key,myObject);
            System.out.println("    ");
        }
    }
输出
创建对象
激活对象,借用时调用
判断对象是否可用
16:47:23.502cry 0
钝化对象,归还时调用
    
激活对象,借用时调用
判断对象是否可用
16:47:23.502cry 1
钝化对象,归还时调用
    
激活对象,借用时调用
判断对象是否可用
销毁对象
创建对象
激活对象,借用时调用
判断对象是否可用
16:47:23.529cry 0
钝化对象,归还时调用
代码解释

上面测试代码使用的是 apache pool 中带有 key 的对象池,还有一种不需要使用 key 的对象池

首先定义了自己的资源类和 key 类

自定义一个 Factory 类,实现了 KeyedPooledObjectFactory 接口,注意泛型中分别是自定义的 key 类和资源类。工厂类方法中定义了几个方法,分别对应资源类的各个生命周期,在这里写自定义的创建、销毁等逻辑。

封装对象池工具,初始化一个 GenericKeyedObjectPoolConfig 配置类,定义了对象池的各种参数。新建一个 GenericKeyedObjectPool 类,入参是自定义的工厂类实例和配置类。

使用时,直接调用 GenericKeyedObjectPool 的 borrowObject 方法借用资源对象,日志可以看出,首次借用时会新建对象,之后再次使用时不用新建。根据自己的配置,在创建或者借用时会调用校验方法校验对象是否可以,如果不可用会调用工厂类的 destroyObject 方法销毁对象,然后新建对象。用完对象后,调用 GenericKeyedObjectPool 的 returnObject 方法归还对象,以便下次使用。

原理

从 GenericKeyedObjectPool 的 borrowObject 方法进入源码查看:

    @Override
    public T borrowObject(final K key) throws Exception {
        return borrowObject(key, getMaxWaitDuration().toMillis());
    }

此方法调用了另一个带有超时时间参数的方法

    /**
     * Borrows an object from the sub-pool associated with the given key using
     * the specified waiting time which only applies if
     * {@link #getBlockWhenExhausted()} is true.
     * <p>
     * If there is one or more idle instances available in the sub-pool
     * associated with the given key, then an idle instance will be selected
     * based on the value of {@link #getLifo()}, activated and returned.  If
     * activation fails, or {@link #getTestOnBorrow() testOnBorrow} is set to
     * {@code true} and validation fails, the instance is destroyed and the
     * next available instance is examined.  This continues until either a valid
     * instance is returned or there are no more idle instances available.
     * </p>
     * <p>
     * If there are no idle instances available in the sub-pool associated with
     * the given key, behavior depends on the {@link #getMaxTotalPerKey()
     * maxTotalPerKey}, {@link #getMaxTotal() maxTotal}, and (if applicable)
     * {@link #getBlockWhenExhausted()} and the value passed in to the
     * {@code borrowMaxWaitMillis} parameter. If the number of instances checked
     * out from the sub-pool under the given key is less than
     * {@code maxTotalPerKey} and the total number of instances in
     * circulation (under all keys) is less than {@code maxTotal}, a new
     * instance is created, activated and (if applicable) validated and returned
     * to the caller. If validation fails, a {@code NoSuchElementException}
     * will be thrown.
     * </p>
     * <p>
     * If the associated sub-pool is exhausted (no available idle instances and
     * no capacity to create new ones), this method will either block
     * ({@link #getBlockWhenExhausted()} is true) or throw a
     * {@code NoSuchElementException}
     * ({@link #getBlockWhenExhausted()} is false).
     * The length of time that this method will block when
     * {@link #getBlockWhenExhausted()} is true is determined by the value
     * passed in to the {@code borrowMaxWait} parameter.
     * </p>
     * <p>
     * When {@code maxTotal} is set to a positive value and this method is
     * invoked when at the limit with no idle instances available under the requested
     * key, an attempt is made to create room by clearing the oldest 15% of the
     * elements from the keyed sub-pools.
     * </p>
     * <p>
     * When the pool is exhausted, multiple calling threads may be
     * simultaneously blocked waiting for instances to become available. A
     * "fairness" algorithm has been implemented to ensure that threads receive
     * available instances in request arrival order.
     * </p>
     *
     * @param key pool key
     * @param borrowMaxWaitMillis The time to wait in milliseconds for an object
     *                            to become available
     *
     * @return object instance from the keyed pool
     *
     * @throws NoSuchElementException if a keyed object instance cannot be
     *                                returned because the pool is exhausted.
     *
     * @throws Exception if a keyed object instance cannot be returned due to an
     *                   error
     */
    public T borrowObject(final K key, final long borrowMaxWaitMillis) throws Exception {
        assertOpen();

        final AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) &&
                (getNumActive() > getMaxTotal() - 3)) {
            removeAbandoned(ac);
        }

        PooledObject<T> p = null;

        // Get local copy of current config so it is consistent for entire
        // method execution
        final boolean blockWhenExhausted = getBlockWhenExhausted();

        boolean create;
        final long waitTimeMillis = System.currentTimeMillis();
        final ObjectDeque<T> objectDeque = register(key);

        try {
            while (p == null) {
                create = false;
                p = objectDeque.getIdleObjects().pollFirst();
                if (p == null) {
                    p = create(key);
                    if (p != null) {
                        create = true;
                    }
                }
                if (blockWhenExhausted) {
                    if (p == null) {
                        if (borrowMaxWaitMillis < 0) {
                            p = objectDeque.getIdleObjects().takeFirst();
                        } else {
                            p = objectDeque.getIdleObjects().pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
                        }
                    }
                    if (p == null) {
                        throw new NoSuchElementException(appendStats(
                                "Timeout waiting for idle object, borrowMaxWaitMillis=" + borrowMaxWaitMillis));
                    }
                } else if (p == null) {
                    throw new NoSuchElementException(appendStats("Pool exhausted"));
                }
                if (!p.allocate()) {
                    p = null;
                }

                if (p != null) {
                    try {
                        factory.activateObject(key, p);
                    } catch (final Exception e) {
                        try {
                            destroy(key, p, true, DestroyMode.NORMAL);
                        } catch (final Exception e1) {
                            // Ignore - activation failure is more important
                        }
                        p = null;
                        if (create) {
                            final NoSuchElementException nsee = new NoSuchElementException(appendStats("Unable to activate object"));
                            nsee.initCause(e);
                            throw nsee;
                        }
                    }
                    if (p != null && getTestOnBorrow()) {
                        boolean validate = false;
                        Throwable validationThrowable = null;
                        try {
                            validate = factory.validateObject(key, p);
                        } catch (final Throwable t) {
                            PoolUtils.checkRethrow(t);
                            validationThrowable = t;
                        }
                        if (!validate) {
                            try {
                                destroy(key, p, true, DestroyMode.NORMAL);
                                destroyedByBorrowValidationCount.incrementAndGet();
                            } catch (final Exception e) {
                                // Ignore - validation failure is more important
                            }
                            p = null;
                            if (create) {
                                final NoSuchElementException nsee = new NoSuchElementException(
                                        appendStats("Unable to validate object"));
                                nsee.initCause(validationThrowable);
                                throw nsee;
                            }
                        }
                    }
                }
            }
        } finally {
            deregister(key);
        }

        updateStatsBorrow(p, Duration.ofMillis(System.currentTimeMillis() - waitTimeMillis));

        return p.getObject();
    }

先解释一下注释:

从子资源池中根据 key 拿到对象,当 getBlockWhenExhausted() 方法返回 true 时,如果没有对象则会等待参数给定的时间

当根据key找到资源对象时会根据 getLifo() 返回的方法决定以何种顺序取出资源,并根据配置检测对象是否可用,如果不可用就销毁,并取下一个,一直到取出可用对象或者没有可用对象

如果没有可用对象,就根据配置的最大对象数量信息,决定是否新建对象并返回。

如果根据 key 没有找到对象而且容量满了,就销毁一部分,然后创建新资源

在看具体代码:

先根据 AbandonedConfig 把长时间不用的资源丢掉

根据 key 拿到资源队列

从队列中拿资源,拿到的是 null 就创建一个新的

如果创建失败但设置了可以等待,则等带一段时间,等待别的线程用完归还资源

如果还获取不到就抛出异常

能拿到就分配资源:改变资源状态,记录资源借用时间等

返回资源

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值