当前位置: 首页 > 后端技术 > Java

基于Apache组件,分析池塘中对象池

时间:2023-04-01 14:40:45 Java

的原理:Object;一、设计与原理1、基本案例首先来看一个基于common-pool2对象池组件的应用案例,主要包括工厂类、对象池、对象A核心角色,以及池化对象的使用过程:importorg.apache.commons.pool2.BasePooledObjectFactory;导入org.apache.commons.pool2.PooledObject;导入org.apache.commons.pool2.impl.DefaultPooledObject;导入org.apache.commons.pool2.impl.GenericObjectPool;导入org.apache.commons.pool2.impl.GenericObjectPoolConfig;导入org.slf4j.Logger;导入org.slf4j.LoggerFactory;公共类ObjPool{publicstaticvoidmain(String[]args)throwsException{//声明对象池DevObjPooldevObjPool=newDevObjPool();//借用池中的对象DevObjdevObj=devObjPool.borrowObject();System.out.println("Idle="+devObjPool.getNumIdle()+";Active="+devObjPool.getNumActive());//使用对象devObj.devObjInfo();//返回对象池devObjPool.returnObject(devObj);System.out.println("Idle="+devObjPool.getNumIdle()+";Active="+devObjPool.getNumActive());//查看对象池System.out.println(devObjPool.listAllObjects());}}/***对象定义*/classDevObj{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(DevObj.class);publicDevObj(){logger.info("build...dev...obj");}publicvoiddevObjInfo(){logger.info("dev...obj...info");}}/***ObjectFactory*/classDevObjFactoryextendsBasePooledObjectFactory{@OverridepublicDevObjcreate()throwsException{//创建对象returnnewDevObj();}@OverridepublicPooledObjectwrap(DevObjdevObj){//池化对象returnnewDefaultPooledObject<>(devObj);}}/***对象池*/classDevObjPoolextendsGenericObjectPool{publicDevObjPool(){super(newDevObjFactory(),newGenericObjectPoolConfig<>());}}在这种情况下,对象是完全自定义的;在对象工厂中,重写了两个核心方法:创建和打包以创建池化对象;对象池的构建依赖于定义的对象工厂,配置由组件提供的通用配置类;可通过调整对象实例化的时间和创建对象的数量,初步了解对象池的原理2、接口设计1.1PooledObjectFactory接口工厂类,负责对象的实例化、创建、验证、销毁、状态管理等;案例中的BasePooledObjectFactory类是这个接口的基本实现;1.2ObjectPool接口对象池,并继承Closeable接口,管理对象生命周期,获取活动和空闲对象的数据信息;例中,GenericObjectPool类是这个接口的实现,是可配置的1.3PooledObject接口池化对象,基于wrapper类在对象池中维护,并维护一些额外的信息用于跟踪,比如时间,状态;案例中,DefaultPooledObject包装类用于实现该接口,并且是线程安全的,注意类中工厂的重写;3.运行原理通过对象池获取对象,对象可能是工厂新创建的,也可能是空闲对象;当成功获取并使用该对象时,需要将其归还;在案例执行过程中,不断查询对象池中空闲和活跃对象的数量,以监控池中的变化。二、结构分析1、对象池publicGenericObjectPool(finalPooledObjectFactoryfactory,finalGenericObjectPoolConfigconfig);在完整的构造方法中,涉及三个核心对象:工厂对象、配置对象、双端阻塞队列;通过这些对象创建一个新的对象池;config中提供了一些简单的默认配置:如maxTotal、maxIdle、minIdle等,也可以扩展自定义配置;2.双端队列privatefinalLinkedBlockingDeque>idleObjects;publicGenericObjectPool(finalPooledObjectFactoryfactory,finalGenericObjectPoolConfigconfig){idleObjects=newLinkedBlockingDeque<>(config.getFairness());}LinkedBlockingDeque支持对队列首尾元素进行操作,如添加和删除等;操作需要主锁加锁,基于两个状态锁进行协作;//团队的第一个节点privatetransientLinkedBlockingDeque.Nodefirst;//队尾节点privatetransientLinkedBlockingDeque.Nodelast;//主锁privatefinalInterruptibleReentrantLocklock;//非空状态锁privatefinalConditionnotEmpty;//非满状态锁privatefinalConditionnotFull;链表和队列的特点在之前的文章中已经分别分析过了。这里的源码在JDK容器中也很常见,这里不再赘述。对象池的整个结构有了大概的轮廓之后,我们再仔细看看对象管理逻辑。三、对象管理1、添加对象创建一个新的对象,放入池中,通常用于需要预加载的场景;它涉及两个核心操作:对象的工厂创建、对象池管理;publicvoidGenericObjectPool.addObject()抛出异常;2。借用对象publicTGenericObjectPool.borrowObject(finallongborrowMaxWaitMillis)throwsException;首先从队列中获取对象;如果没有,调用工厂创建方法,然后进行池管理;获取对象后,状态会变为ALLOCATEDisinuse;最后经过工厂的确认,对象获取动作完成;3.返回对象publicvoidGenericObjectPool.returnObject(finalTobj);返回对象时,先转换为池化对象,并标记RETURNING状态;如果失败,销毁对象,重新维护对象池中可用的空闲对象;最后一个对象被标记为空闲,如果没有超过最大空闲数,则该对象被放在队列的一端;4.对象状态池化对象的状态在PooledObjectState类中被枚举和描述。在图中,只显示了一些状态转换。更多细节请参考状态类;可以参考上面案例中使用的DefaultPooledObject的默认池化对象类中的相关方法,结合状态枚举,可以理解不同状态之间的校验转换。4、Redis使用Lettuce作为高级Redis客户端组件。通信层使用Netty组件,线程安全,支持同步和异步模式,支持集群和哨兵模式。作为目前项目中的常用配置,其底层对象池基于common-pool2组件。1、配置管理基于如下配置,即使用Lettuce组件,涉及pool几个参数的配置:minimumidle,maximumactive,maximumidle;这里可以对比GenericObjectPoolConfig中的配置:spring:redis:host:${REDIS_HOST:127.0.0.1}lettuce:pool:min-idle:10max-active:100max-idle:1002源码分析围绕着特性展开的对象池,所以自然而然地追寻源码中的核心角色类:configuration、factory、object;从上面的配置参数中,可以很容易的找到以下几个类:2.1Configurationconversion//Connection配置类LettuceConnectionConfigurationextendsRedisConnectionConfiguration{privatestaticclassPoolBuilderFactory{//构建对象池配置privateGenericObjectPoolConfiggetPoolConfig(RedisProperties.Poolproperties){GenericObjectPoolConfigconfig=newGenericObjectPoolConfig<>();config.setMaxTotal(properties.getMaxActive());config.setMaxIdle(properties.getMaxIdle());config.setMinIdle(properties.getMinIdle());返回配置;这里将配置文件中Redis的相关参数内置到GenericObjectPoolConfig类中,即配置加载过程;2.2对象池构造类LettucePoolingConnectionProviderimplementsLettucePoolingConnectionProviderimplementsLettuceConnectionProvider{//对象池核心角颜色privatefinalGenericObjectPoolConfigpoolConfig;私有最终BoundedPoolConfigasyncPoolConfig;privatefinalMap,GenericObjectPool>pools=newConcurrentHashMap(32);LettucePoolingConnectionProvider(LettuceConnectionProvider提供者,LettucePoolingClientConfiguration配置){this.poolConfig=clientConfiguration.getPoolConfig();this.asyncPoolConfig=CommonsPool2ConfigConverter.bounded(this.config);}}在构造方法中获取对象池的配置信息。这里并没有直接实例化pool对象,而是使用ConcurrentHashMap容器??进行动态维护;2.3对象管理类LettucePoolingConnectionProviderimplementsLettuceConnectionProvider{//获取Redis连接public>TgetConnection(ClassconnectionType){GenericObjectPoolpool=(GenericObjectPool)this.pools.computeIfAbsent();StatefulConnectionconnection=(StatefulConnection)pool.borrowObject();}//发布Redis连接publicvoidrelease(StatefulConnectionconnection){GenericObjectPool>pool=(GenericObjectPool)this.poolRef.remove(connection);}}在获取pool对象时,如果不存在则根据相关配置创建一个pool对象,维护在Map容器??中,然后从pool中借用Redis连接对象;释放对象时,先判断对象所属的池,将对象归还给对应的池。切入一个简单的案例,主要分析common-pool2这个组件的源码逻辑关于:pool,factory,configuration,objectmanagement,参考其在Redis中的实践,这只是冰山一角,这样的general-用途和广泛应用的组件,值得不时阅读源代码,真是令人惊叹其巧妙的设计。5.参考源码应用仓库:https://gitee.com/cicadasmile/butte-flyer-parent组件打包:https://gitee.com/cicadasmile/butte-frame-parent