对象池 apache commons-pool2 入门详解

前言

大多时候,我们获取对象的方法都是直接 new 一个。但是,对于大对象的构造,或者构造耗时比较久的对象,我们每次要使用都去 new 一个是很不科学的。比如数据库的连接对象、Redis 的连接对象、Http 连接请求对象等等。在设计模式中有一个专门的模式来解决这种场景下的问题,即“享元模式”。

“享元模式”其实很好理解,也就是构造一个对象池,这个对象池中维护一定数量的对象,需要的时候就从这个对象池中获取对象,使用完后返还给对象池。这样就避免构造对象所带来的耗时,提升了系统的性能。

设计这样的一个对象池看起来好像并不难,甚至觉的只需要一个 List 就可以做到。但是,如果考虑到系统的伸缩性,比如在系统忙时可能需要对象池中有足够的对象可以被拿来使用,以保证系统大多时候应该等待对象而进入阻塞,同时,在系统闲时又不需要太多的对象存放在对象池中,这时候就需要释放一些对象。另外,还需要考虑对象何时构造,何时销毁,对象异常的处理等问题。

为了让大多 java 程序员不重复造轮子,apache 开发了一个库,专门给需要对象池的程序提供一个底层的支持。这个库也就是 apche commons-pool2,使用这个库,我们只需要关注对象的生成、销毁、校验等操作就可以了。对象池的具体实现细节都交给 commons-pool2 中的具体对象池实现类来完成。

Maven 地址

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

commons-pool2 核心接口

apache commons-pool2 包提供了一个通用的对象池技术的实现。可以很方便的基于它来实现自己的对象池,比如 DBCP 和 Jedis 他们的内部对象池的实现就是依赖于 commons-pool2 。

commons-pool2 由三大模块组成:ObjectPool 、PooledObject 和 PooledObjectFactory 。

ObjectPool :提供所有对象的存取管理。

PooledObject :池化的对象,是对真正对象的一个包装,加上了对象的一些其他信息,包括对象的状态(已用、空闲),对象的创建时间等。其实现要求是线程安全的。

PooledObjectFactory :工厂类,负责池化对象的创建,对象的初始化,对象状态的销毁和对象状态的验证。其实现要求是线程安全的。

通常,ObjectPool 会持有 PooledObjectFactory ,将具体的对象的创建、初始化、销毁等任务委托给 PooledObjectFactory 处理,工厂操作的对象是 PooledObject ,即具体的 Object 的包装类。因此,获取对象依赖 ObjectPool 而不是 PooledObjectFactory 。

实现对象池

ObjectPool 的主要方法:

//从对象池中获取对象的方法
T borrowObject() throws Exception, NoSuchElementException,
        IllegalStateException;
//将对象返还给对象池
void returnObject(T obj) throws Exception;
//让对象失效
void invalidateObject(T obj) throws Exception;
//往对象池中新增一个对象
void addObject() throws Exception, IllegalStateException,
        UnsupportedOperationException;
//获取当前闲置在对象池中的对象数量,即没有被拿走使用的对象数量
int getNumIdle();
//获取已经在使用中的对象数量,即被使用者从对象池中拿走使用的数量
int getNumActive();
//清空对象池中闲置的所有对象
void clear() throws Exception, UnsupportedOperationException;
//关闭对象池
void close();

PooledObject ,抽象了对象池中对象应该具备的一些属性。注意,这个对象并不是我们真正要存的对象,而是经过一层封装的对象,那么真正存放在对象池的其实都是经过封装过的对象,即 PooledObject 对象。

public interface PooledObject<T> extends Comparable<PooledObject<T>> {
    T getObject();
    long getCreateTime();
    long getActiveTimeMillis();
    long getIdleTimeMillis();
    long getLastBorrowTime();
    long getLastReturnTime();
    long getLastUsedTime();
    @Override
    int compareTo(PooledObject<T> other);
    @Override
    boolean equals(Object obj);
    @Override
    int hashCode();
    String toString();
    boolean startEvictionTest();
    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
    boolean allocate();
    boolean deallocate();
    void invalidate();
    void setLogAbandoned(boolean logAbandoned);
    void use();
    void printStackTrace(PrintWriter writer);
    PooledObjectState getState();
    void markAbandoned();
    void markReturning();
}

PooledObjectFactory 抽象工厂模型:

public interface PooledObjectFactory<T> {
  //构造一个封装对象
  PooledObject<T> makeObject() throws Exception;
  //销毁对象
  void destroyObject(PooledObject<T> p) throws Exception;
  //验证对象是否可用
  boolean validateObject(PooledObject<T> p);
  //激活一个对象,使其可用用
  void activateObject(PooledObject<T> p) throws Exception;
   //钝化一个对象,也可以理解为反初始化
  void passivateObject(PooledObject<T> p) throws Exception;
}

以 Http 连接请求为例

创建对象工厂 PooledHttpClientFactory :

public class PooledHttpClientFactory implements PooledObjectFactory<HttpClient> {

    @Override
    public PooledObject<HttpClient> makeObject() throws Exception {
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        return new DefaultPooledObject<>(httpClient);
    }

    @Override
    public void destroyObject(PooledObject<HttpClient> pooledObject) throws Exception {
        //不处理
    }

    @Override
    public boolean validateObject(PooledObject<HttpClient> pooledObject) {
        return true;
    }

    @Override
    public void activateObject(PooledObject<HttpClient> pooledObject) throws Exception {
        //不处理
    }

    @Override
    public void passivateObject(PooledObject<HttpClient> pooledObject) throws Exception {
        //不处理
    }
}

测试:

    public static void main(String[] args) {
        GenericObjectPoolConfig httpClientPoolConfig = new GenericObjectPoolConfig();
        httpClientPoolConfig.setMaxTotal(100);
        httpClientPoolConfig.setMaxIdle(10);

        PooledHttpClientFactory httpClientFactory = new PooledHttpClientFactory();

        GenericObjectPool<HttpClient> httpClientPool = new GenericObjectPool<>(httpClientFactory, httpClientPoolConfig);

        try {
            HttpClient httpClient = httpClientPool.borrowObject();
            try {
                RequestBuilder requestBuilder = RequestBuilder.get("https://www.oschina.net");
                requestBuilder.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36");
                HttpUriRequest httpUriRequest = requestBuilder.build();

                HttpClientContext httpClientContext = new HttpClientContext();

                HttpResponse httpResponse = httpClient.execute(httpUriRequest, httpClientContext);

                byte[] contentBytes = IOUtils.toByteArray(httpResponse.getEntity().getContent());
                System.out.println("获取的网页源码:" + new String(contentBytes, "utf-8"));

                httpClientPool.returnObject(httpClient);
            } catch (Exception ex) {
                httpClientPool.invalidateObject(httpClient);
                ex.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Config 配置项

属性说明
maxTotal允许创建对象的最大数量,默认值 8 ,-1 代表无数量限制( int 类型)
maxIdle最大空闲资源数,默认值 8
minIdle最小空闲资源数,默认值 0 ,软标准情况下,至少留下几个 object 继续保持 idle(节省开销),软标准,结合 idleSoftEvictTime 和 timeBetweenEvictionRunsMillis 使用
lifo对象池存储空闲对象是使用的 LinkedBlockingDeque ,它本质上是一个支持 FIFO 和 FILO 的双向的队列,commons-pool2 中的 LinkedBlockingDeque 不是 Java 原生的队列,而有 commons-pool2 重新写的一个双向队列。如果为 true ,表示使用 FIFO 获取对象(资源的存取数据结构,默认值 true ,true 资源按照栈结构存取,false 资源按照队列结构存取)
fairness从池中获取/返还对象时是否使用公平锁机制,默认为 false
maxWaitMillis获取对象时的等待时间,单位毫秒。当 blockWhenExhausted 配置为 true 时,此值有效。 -1 代表无时间限制,一直阻塞直到有可用的资源( long 类型)
minEvictableIdleTimeMillis对象空闲的最小时间,达到此值后空闲对象将可能会被移除。-1 表示不移除;默认 30 分钟,在资源回收策略中,会使用到
softMinEvictableIdleTimeMillis同上,额外的条件是池中至少保留有 minIdle 所指定的个数的对象在资源回收策略中,会使用到
numTestsPerEvictionRun资源回收线程执行一次回收操作,回收资源的数量。默认 3 。当设置为 0 时,不回收资源。 设置为小于 0 时,回收资源的个数为 (int)Math.ceil(池中空闲资源个数/Math.abs(numTestsPerEvictionRun));设置为大于 0 时,回收资源的个数为 Math.min(numTestsPerEvictionRun,池中空闲的资源个数)
testOnCreate创建对象时是否调用 factory.validateObject 方法,默认 false
testOnBorrow取对象时是否调用 factory.validateObject 方法,默认 false
testOnReturn返还对象时是否调用 factory.validateObject 方法,默认 false 。当将资源返还个资源池时候,验证资源的有效性,调用 factory.validateObject()方法,如果无效,则调用 factory.destroyObject() 方法
testWhileIdle池中的闲置对象是否由逐出器验证。无法验证的对象将从池中删除销毁。默认 false
timeBetweenEvictionRunsMillis回收资源线程的执行周期,默认 -1 ,表示不启用回收资源线程,即不开启驱逐线程,所以与之相关的参数是没有作用的
minEvictableIdleTimeMillis最小的驱逐时间,对象最小的空闲时间。如果为小于等于 0 ,取 Long 的最大值,如果大于 0 ,当空闲的时间大于这个值时,执行移除这个对象操作。默认值是 1000L*60L*30L;即 30 分钟。可以避免(连接)泄漏
SoftMinEvictableIdleTimeMillis也是最小的驱逐时间,对象最小的空间时间,如果小于等于 0 ,取 Long 的最大值,如果大于 0 ,当对象的空闲时间超过这个值,并且当前空闲对象的数量大于最小空闲数量(minIdle)时,执行移除操作。这个和上面的 minEvictableIdleTimeMillis 的区别是,它会保留最小的空闲对象数量。而上面的不会,是强制性移除的。默认值是 -1 ,通常该值设置的比 minEvictableIdleTimeMillis 要小
evictionPolicyClassName资源回收策略,默认值 org.apache.commons.pool2.impl.DefaultEvictionPolicy 驱逐线程使用的策略类名,之前的 minEvictableIdleTimeMillis 和 softMinEvictableIdleTimeMillis 就是默认策略 DefaultEvictionPolicy 的实现,这个两个参数,在资源回收策略中,会使用到
blockWhenExhausted资源耗尽时,是否阻塞等待获取资源,默认 true
whenExhaustedAction指定在池中借出对象的数目已达极限的情况下,调用它的 borrowObject 方法时的行为。可以选用的值有:GenericObjectPool.WHEN_EXHAUSTED_BLOCK ,表示等待;GenericObjectPool.WHEN_EXHAUSTED_GROW ,表示创建新的实例(不过这就使 maxActive 参数失去了意义);GenericObjectPool.WHEN_EXHAUSTED_FAIL ,表示抛出一 java.util.NoSuchElementException 异常
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值