美文网首页
SpringBoot2.x自定义实现PooledObjectFa

SpringBoot2.x自定义实现PooledObjectFa

作者: 小胖学编程 | 来源:发表于2021-05-11 16:16 被阅读0次

    在项目中,我们经常听到连接池,例如数据库连接池,jedis连接池等等。

    apache提供了一个公共连接池pool2包提供了一个通用的对象池技术实现。可以很方便的基于它来实现自己的对象池,比如DBCP和Jedis他们的内部对象池的实现就是依赖于Common-pool2。对象的创建和销毁在一定程度上会消耗系统的资源,虽然jvm的性能在近几年已经得到了很大的提高,对于多数对象来说,没有必要利用对象池技术来进行对象的创建和管理。但是对于有些对象来说,其创建的代价还是比较昂贵的,比如线程、tcp连接、数据库连接等对象,因此对象池技术还是有其存在的意义。

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

    1. 核心代码

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

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

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

    还需要注意:

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

    1.1 PooledObjectFactory类

    提供了PooledObjectFactory <T>接口类,即池对象的工厂类,该类定义了对池对象的操作方法,比如创建、校验、销毁、激活、卸载。

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

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

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

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

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

    1.2 GenericObjectPoolConfig

    对象池配置类。定义了对象池的基本信息。

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

    1.3 ObjectPool

    对象池是commons-pool的核心接口,用来维护"对象列表"的存取;其中GenericObjectPool是其实现类,它已经实现了相关的功能.

    Object borrowObject() : 从Pool获取一个对象,此操作将导致一个"对象"从Pool移除(脱离Pool管理),调用者可以在获得"对象"引用后即可使用,且需要在使用结束后"归还"。

    1.4 对象池的创建

    public GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 
    

    此方法创建一个GenericObjectPool实例,GenericObjectPool类已经实现了和对象池有关的所有核心操作,开发者可以通过继承或者封装的方式来使用它.通过此构造函数,我们能够清晰的看到,一个Pool中需要指定PoolableObjectFactory 实例,以及此对象池的Config信息.PoolableObjectFactory主要用来"创建新对象",比如当对象池中的对象不足时,可以使用 PoolableObjectFactory.makeObject()方法来创建对象,并交付给Pool管理。

    此构造函数实例化了一个LinkedList作为"对象池"容器,用来存取"对象".此外还会根据timeBetweenEvictionRunsMillis的值来决定是否启动一个后台线程,此线程用来周期性扫描pool中的对象列表,已检测"对象池中的对象"空闲(idle)的时间是否达到了阀值,如果是,则移除此对象.

    2. 实战

    连接对象

    /**
     * 一个连接,创建复杂,可以想象为数据库或者redis或者socket
     */
    @Getter
    @Slf4j
    public class MyConnection {
    
        private String name;
    
        private boolean connected;
    
        public MyConnection(String name) {
            this.name = name;
            connect();
        }
    
    
        public void connect() {
            log.info("{} is connect", name);
            connected = true;
        }
    
        public void close() {
            log.info("{} is close", name);
            connected = false;
        }
    
        //业务方法
        public void execute() {
            try {
                Thread.sleep(300L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("{}-执行业务方法...", name);
        }
    
    }
    

    池对象工具类

    /**
     * 池对象工厂类
     */
    @Slf4j
    public class MyConnectionPoolObjectFactory implements PooledObjectFactory<MyConnection> {
        private static int count = 0;
    
        @Override
        public PooledObject<MyConnection> makeObject() throws Exception {
            MyConnection myConnection = new MyConnection(generateName());
            return new DefaultPooledObject<>(myConnection);
        }
    
        @Override
        public void destroyObject(PooledObject<MyConnection> p) throws Exception {
            MyConnection myConnection = p.getObject();
            log.info("{},destroyObject...", myConnection.getName());
            myConnection.close();
        }
    
        @Override
        public boolean validateObject(PooledObject<MyConnection> p) {
            MyConnection myConnection = p.getObject();
            log.info("{},validateObject...", myConnection.getName());
            return myConnection.isConnected();
        }
    
        @Override
        public void activateObject(PooledObject<MyConnection> p) throws Exception {
            MyConnection myConnection = p.getObject();
            log.info("{},activateObject...", myConnection.getName());
        }
    
        @Override
        public void passivateObject(PooledObject<MyConnection> p) throws Exception {
            MyConnection myConnection = p.getObject();
            log.info("{},passivateObject...", myConnection.getName());
        }
    
        private synchronized String generateName() {
            return "conn_" + (++count);
        }
    }
    

    测试方法

    /**
     * 对象池的使用
     */
    @Slf4j
    public class TestPoolObject {
    
        //对象池
        public static GenericObjectPool<MyConnection> connectionPool;
    
        static {
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            poolConfig.setMaxTotal(5);
            poolConfig.setMinIdle(0);
            poolConfig.setMaxIdle(0);
            poolConfig.setMaxWaitMillis(-1);
            MyConnectionPoolObjectFactory objectFactory = new MyConnectionPoolObjectFactory();
            connectionPool = new GenericObjectPool<>(objectFactory, poolConfig);
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    MyConnection myConnection = null;
                    //获取注解
                    try {
                        //获取连接
                        myConnection = connectionPool.borrowObject();
                        myConnection.execute();
                    } catch (Exception e) {
                        try {
                            connectionPool.invalidateObject(myConnection);
                            myConnection = null;
                        } catch (Exception ex) {
                            log.error("", e);
                        }
                        log.error("", e);
                    } finally {
                        //归还连接
                        if (myConnection != null) {
                            connectionPool.returnObject(myConnection);
                        }
                    }
                }).start();
            }
        }
    }
    
    • 调用borrowObject方法获取连接,调用returnObject方法归还连接;
    • maxIdle和minIdle都改为0,就是在连接不用时立即真正归还连接,对于数据库连接来说就是关闭物理连接,而 maxWait改为-1,就是如果没有申请到连接就永远等待,真实情况下不会把maxIdle和 minIdle都设为0的。

    文章参考

    对象池GenericObjectPoolConfig

    commons-pool实战之 GenericObjectPool和GenericKeyedObjectPool

    相关文章

      网友评论

          本文标题:SpringBoot2.x自定义实现PooledObjectFa

          本文链接:https://www.haomeiwen.com/subject/zbngdltx.html