【自定义对象池】GenericObjectPool

GenericObjectPool

​ Apache Commons Pool是一个对象池的框架,他提供了一整套用于实现对象池化的API。它提供了三种对象池:GenericKeyedObjectPool,SoftReferenceObjectPool和GenericObjectPool,其中GenericObjectPool是我们最常用的对象池,内部实现也最复杂。

  • commons.pool2 对象池的使用的依赖
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.3</version>
</dependency>

1. 核心思想

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

  • ObjectPool:提供所有对象的存取管理。
  • PooledObject:池化的对象,是对对象的一个包装,加上了对象的一些其他信息,包括对象的状态(已用、空闲),对象的创建时间等。
  • PooledObjectFactory:工厂类,负责池化对象的创建,对象的初始化,对象状态的销毁和对象状态的验证。

ObjectPool会持有PooledObjectFactory,将具体的对象的创建、初始化、销毁等任务交给它处理,其操作对象是PooledObject,即具体的Object的包装类。

还需要注意:

  • GenericObjectPoolConfig:提供对象池的配置信息。

2. 核心接口ObjectPool

public class GenericObjectPool<T> extends BaseGenericObjectPool<T> implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {
  
}

ObjectPool接口核心方法:

  • addObject方法:往池中添加一个对象

  • borrowObject方法:从池中借走到一个对象

  • returnObject方法:把对象归还给对象池

  • invalidateObject:销毁一个对象。这个方法才会将对象从池子中删除,这其中最重要的就是释放对象本身持有的各种资源。

  • getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。

  • getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。

  • clear:清理对象池。注意是清理不是清空,该方法要求的是,清理所有空闲对象,释放相关资源。

  • close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。

3. 对象工厂BasePooledObjectFactory

​ 对象的创建需要通过对象工厂来创建,对象工厂需要实现BasePooledObjectFactory接口。ObjectPool接口中往池中添加一个对象,就需要使用对象工厂来创建一个对象。

继承这个抽象类之后必须重写的两个方法:

  • create():创建对象
  • wrap(T var1):封装对象
public abstract class BasePooledObjectFactory<T> extends BaseObject implements PooledObjectFactory<T> {
    public BasePooledObjectFactory() {}

    public void activateObject(PooledObject<T> p) throws Exception {}

    public abstract T create() throws Exception;

    public void destroyObject(PooledObject<T> p) throws Exception {}

    public PooledObject<T> makeObject() throws Exception {
        return this.wrap(this.create());
    }

    public void passivateObject(PooledObject<T> p) throws Exception {}

    public boolean validateObject(PooledObject<T> p) {
        return true;
    }

    public abstract PooledObject<T> wrap(T var1);
}

3.1 PooledObjectFactory 接口

BasePooledObjectFactory实现了PooledObjectFactory 接口

即池对象的工厂类,该类定义了对池对象的操作方法,比如创建、校验、销毁、激活、卸载。

public interface PooledObjectFactory<T> {

    /**
     * 创建一个可由池提供服务的实例,并将其封装在由池管理的PooledObject中。
     */
    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;
}

接口说明:

  • Object makeObject() :

    创建一个新对象;当对象池中的对象个数不足时,将会使用此方法来"输出"一个新的"对象",并交付给对象池管理。

  • void destroyObject(Object obj) :

    销毁对象,如果对象池中检测到某个"对象"idle的时间超时,或者操作者向对象池"归还对象"时检测到"对象"已经无效,那么此时将会导致"对象销毁";"销毁对象"的操作设计相差甚远,但是必须明确:当调用此方法时,"对象"的生命周期必须结束.如果object是线程,那么此时线程必须退出;如果object是socket操作,那么此时socket必须关闭;如果object是文件流操作,那么此时"数据flush"且正常关闭.

  • boolean validateObject(Object obj) :

    检测对象是否"有效";Pool中不能保存无效的"对象",因此"后台检测线程"会周期性的检测Pool中"对象"的有效性,如果对象无效则会导致此对象从Pool中移除,并destroy;此外在调用者从Pool获取一个"对象"时,也会检测"对象"的有效性,确保不能讲"无效"的对象输出给调用者;当调用者使用完毕将"对象归还"到Pool时,仍然会检测对象的有效性.所谓有效性,就是此"对象"的状态是否符合预期,是否可以对调用者直接使用;如果对象是Socket,那么它的有效性就是socket的通道是否畅通/阻塞是否超时等.

  • void activateObject(Object obj) :

    "激活"对象,当Pool中决定移除一个对象交付给调用者时额外的"激活"操作,比如可以在activateObject方法中"重置"参数列表让调用者使用时感觉像一个"新创建"的对象一样;如果object是一个线程,可以在"激活"操作中重置"线程中断标记",或者让线程从阻塞中唤醒等;如果 object是一个socket,那么可以在"激活操作"中刷新通道,或者对socket进行链接重建(假如socket意外关闭)等.

  • void passivateObject(Object obj) :

    "钝化"对象,当调用者"归还对象"时,Pool将会"钝化对象";钝化的言外之意,就是此"对象"暂且需要"休息"一下.如果object是一个 socket,那么可以passivateObject中清除buffer,将socket阻塞;如果object是一个线程,可以在"钝化"操作中将线程sleep或者将线程中的某个对象wait.需要注意的时,activateObject和passivateObject两个方法需要对应,避免死锁或者"对象"状态的混乱.

3.2 自定义一个xxx对象工厂类

xxx:为要被管理的对象

private static class xxxClientFactory extends BasePooledObjectFactory<xxx> {

  @Override
  public SftpClient create() {
    return new xxx();
  }

  @Override
  public PooledObject<SftpClient> wrap(xxx xxx) {
    return new DefaultPooledObject<>(xxx);
  }

  @Override
  public boolean validateObject(PooledObject<xxx> p) {
    return p.getObject().test();
  }

  @Override
  public void destroyObject(PooledObject<xxx> p) {
    p.getObject().disconnect();
  }

}

4. 配置类GenericObjectPoolConfig

​ GenericObjectPoolConfig是封装GenericObject池配置的简单“结构”,此类不是线程安全的;它仅用于提供创建池时使用的属性。大多数情况,可以使用GenericObjectPoolConfig提供的默认参数就可以满足日常的需求,GenericObjectPoolConfig是一个抽象类,实际应用中需要新建配置类,然后继承它。

  • maxActive: 链接池中最大连接数,默认为8.
  • maxIdle: 链接池中最大空闲的连接数,默认为8.
  • minIdle: 连接池中最少空闲的连接数,默认为0.如果当前测试对象的空闲时间大于config中设置的idleSoftEvictTime并且pool中空闲对象的数量大于minIdle,那么就会return true。然后就会回收销毁该对象。
  • 其中EvictionConfig中的idleSoftEvictTime和idleEvictTime的默认值是在BaseObjectPoolConfig中定义的。
  • maxWait: 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时.
  • minEvictableIdleTimeMillis: 连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除。
  • softMinEvictableIdleTimeMillis: 连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留“minIdle”个空闲连接数。默认为-1.
  • numTestsPerEvictionRun: 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
  • testOnBorrow: 向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值.
  • testOnReturn: 向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值.
  • testWhileIdle: 向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值.
  • timeBetweenEvictionRunsMillis: “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
  • whenExhaustedAction: 当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1:
    • -> 0 : 抛出异常,
    • -> 1 : 阻塞,直到有可用链接资源
    • -> 2 : 强制创建新的链接资源

5. 工作原理流程

  1. 构造方法
    当我们执行构造方法时,主要工作就是创建了一个存储对象的LinkedList类型容器,也就是概念意义上的“池”
  2. 从对象池中获取对象
    获取池中的对象是通过borrowObject()命令,源码比较复杂,简单而言就是去LinkedList中获取一个对象,如果不存在的话,要调用构造方法中第一个参数Factory工厂类的makeObject()方法去创建一个对象再获取,获取到对象后要调用validateObject方法判断该对象是否是可用的,如果是可用的才拿去使用。LinkedList容器减一
  3. 归还对象到线程池
    简单而言就是先调用validateObject()方法判断该对象是否是可用的,如果可用则归还到池中,LinkedList容器加一,如果是不可以的则则调用destroyObject方法进行销毁

上面三步就是最简单的流程,由于取和还的流程步骤都在borrowObject和returnObject方法中固定的,所以我们只要重写Factory工厂类的makeObject()和validateObject以及destroyObject方法即可实现最简单的池的管理控制,通过构造方法传入该Factory工厂类对象则可以创建最简单的对象池管理类。这算是比较好的解耦设计模式,借和还的流程如下图所示:

6.对象线程池-快速入门

6.1 依赖包

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

6.2 连接对象类

  • 被对象池管理的对象
  • 可以想象为一个连接,创建复杂,比如数据库或者redis或者socket之类的对象
/**
 * 对象
 */
public class Person {
        
    private Integer id;;

    public Person(Integer id) {
        this.id = id;
    }

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    public void executeTask(){
        System.out.println("对象" + id + ":" + "执行任务");
    }

    @Override
    public String toString() {
        return "Person{" + "id=" + id + '}';
    }
}

6.3 对象池工厂

自定义对象工厂,继承BasePooledObjectFactory类,并重写create()、wrap()方法

import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 对象池工厂
 */
public class PersonPoolFactory extends BasePooledObjectFactory<Person> {

    // AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减
    private AtomicInteger atomicInteger = new AtomicInteger(1);

    /**
     * 在对象池中创建对象
     * @return 自定义对象
     */
    public Person create() {
        return new Person(atomicInteger.getAndAdd(1));
    }

    /**
     * common-pool2 中创建了 DefaultPooledObject 对象对对象池中对象进行的包装。
     * 将我们自定义的对象放置到这个包装中,工具会统计对象的状态、创建时间、更新时间、返回时间、出借时间、使用时间等等信息进行统计
     *
     * @param person 自定义对象
     * @return 对象池
     */
    public PooledObject<Person> wrap(Person person) {
        return new DefaultPooledObject<>(person);
    }

    /**
     * 销毁对象
     * @param p 对象池
     * @throws Exception 异常
     */
    @Override
    public void destroyObject(PooledObject<Person> p) throws Exception {
        super.destroyObject(p);
    }

    /**
     * 校验对象是否可用
     * @param p 对象池
     * @return 对象是否可用结果,boolean
     */
    @Override
    public boolean validateObject(PooledObject<Person> p) {
        return super.validateObject(p);
    }

    /**
     * 激活钝化的对象系列操作
     * @param p 对象池
     * @throws Exception 异常信息
     */
    @Override
    public void activateObject(PooledObject<Person> p) throws Exception {
        super.activateObject(p);
    }

    /**
     * 钝化未使用的对象
     * @param p 对象池
     * @throws Exception 异常信息
     */
    @Override
    public void passivateObject(PooledObject<Person> p) throws Exception {
        super.passivateObject(p);
    }
}

6.4 测试

import club.mydlq.pool.Person;
import club.mydlq.pool.PersonPoolFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 测试对象池的 main 方法
 */
public class Application {

    public static void main(String[] args) {
        // =====================创建线程池=====================
        ExecutorService excutor = Executors.newFixedThreadPool(10);
        // =====================创建对象池=====================
        // 对象池工厂
        PersonPoolFactory personPoolFactory = new PersonPoolFactory();
        // 对象池配置
        GenericObjectPoolConfig<Person> objectPoolConfig = new GenericObjectPoolConfig<>();
        objectPoolConfig.setMaxTotal(5);
        // 对象池
        GenericObjectPool<Person> personPool = new GenericObjectPool<>(personPoolFactory, objectPoolConfig);
        // =====================测试对象池=====================
        // 循环100次,从线程池中取多个多线程执行任务,来测试对象池
        for (int i = 0; i < 15; i++) {
            excutor.submit(new Thread(() -> {
                // 模拟从对象池取出对象,执行任务
                Person person = null;
                try {
                    // 从对象池取出对象
                    person = personPool.borrowObject();
                    // 让对象工作
                    person.executeTask();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 回收对象到对象池
                    if (person != null) {
                        personPool.returnObject(person);
                    }
                }
            }));
        }
    }

}

7.总结

​ Common Pool2 的应用非常广泛,在日常的开发工作中也有很多使用场景。它的整体架构也并不复杂,可以将其简单划分为 3 个角色和相关的配置、状态,掌握起来比较简单。而且 Common Pool2 官方也提供了一些通用的实现,有特殊的开发需求时也可以简单的扩展其提供的抽象类,可以满足大部分的日常开发需求。

posted @ 2023-05-04 16:27  lihewei  阅读(806)  评论(0编辑  收藏  举报
-->