apache-common pool的使用

1|0介绍

Apache commons-pool本质上是"对象池",即通过一定的规则来维护对象集合的容器;commos-pool在很多场景中,用来实现连接池任务worker池等,大家常用的dbcp数据库连接池,也是基于commons-pool实现.

commons-pool实现思想非常简单,它主要的作用就是将对象集合池化,任何通过pool进行对象存取的操作,都会严格按照pool配置(比如池的大小)实时的创建对象/阻塞控制/销毁对象等.它在一定程度上,实现了对象集合的管理以及对象的分发.

  1. 将创建对象的方式,使用工厂模式;
  2. 通过"pool配置"来约束对象存取的时机
  3. 将对象列表保存在队列中(LinkedList)

首选需要声明,不同的"对象池"(或者连接池)在设计上可能存在很大的区别,但是在思想上大同小异,本文主要讲解commons-pool,它和其他"连接池"的区别在此不多讨论.

2|01、对象生命周期

3|02、Config详解

  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 : 强制创建新的链接资源

这些属性均可以在org.apache.commons.pool.impl.GenericObjectPool.Config中进行设定。

4|03、原理解析

4|13.1 四大件

  1. 整个方案从ObjectPoolPooledObjectFactoryPooledObject三个接口展开,
  2. 其中ObjectPool定义了对象池要实现的功能【比如怎么存取,怎么过期】;
  3. PooledObjectFactory定义了被池化的对象的创建,初始化,激活,钝化以及销毁功能;
  4. PooledObject定了一被池化对象的一些附加信息【创建时间,池中状态】;
  5. 大概流程就是由PooledObjectFactory创建的对象经过PooledObject的包装然后放到ObjectPool里面来。

1|0新建资源

1|0回收资源

4|23.1 对象池创建(参考GenericObjectPool)

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

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

if ((getMinEvictableIdleTimeMillis() > 0) && (idleTimeMilis > getMinEvictableIdleTimeMillis())) { removeObject = true; } ... if (removeObject) { try { _factory.destroyObject(pair.value); } catch(Exception e) { // ignored } }

4|33.2 对象工厂PoolableObjectFactory接口

commons-pool通过使用ObjectFactory(工厂模式)的方式将对象池中的对象创建/检测/销毁等特性解耦出来,这是一个非常良好的设计思想.此接口有一个抽象类BasePoolableObjectFactory,可供开发者继承和实现.

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

4|43.3 ObjectPool接口与实现

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

  • T borrowObject() throws Exception, NoSuchElementException,IllegalStateException:从Pool获取一个对象,此操作将导致一个"对象"从Pool移除(脱离Pool管理),调用者可以在获得"对象"引用后即可使用,且需要在使用结束后"归还"。
  • void returnObject(T obj) throws Exception :"归还"对象,当"对象"使用结束后,需要归还到Pool中,才能维持Pool中对象的数量可控,如果不归还到Pool,那么将意味着在Pool之外,将有大量的"对象"存在,那么就使用了"对象池"的意义。
  • void invalidateObject(T obj) throws Exception:销毁对象,直接调用ObjectFactory.destroyObject(obj)
  • void addObject() throws Exception, IllegalStateException,UnsupportedOperationException:开发者可以直接调用addObject方法用于直接创建一个"对象"并添加到Pool中
  • int getNumIdle()://获取对象个数
  • int getNumActive()://获取活跃对象个数
  • void clear() throws Exception, UnsupportedOperationException://清除池,池可用
  • void close()://关闭池,池不可用

4|5PooledObject

T getObject(); long getCreateTime(); long getActiveTimeMillis(); long getIdleTimeMillis(); long getLastBorrowTime(); long getLastReturnTime(); long getLastUsedTime(); int compareTo(PooledObject<T> other); boolean equals(Object obj); 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();

5|0示例

5|1数据库连接池

1|0构建连接工厂

核心方法非常少,开发者只用实现很少方法即可

makeObject 创建资源
activateObject 资源被激活时候调用
passivateObject 资源在回收时候调用
validateObject 当配置了资源检查时候会在创建和回收时候调用
destroyObject 资源在销毁时候调用

public class PooledConnectFactory implements PooledObjectFactory<Connection> { /** * 数据库连接 */ private final String url; /** * 用户名 */ private final String userName; /** * 数据密码 */ private final String password; public PooledConnectFactory(String url, String userName, String password) { this.url = url; this.userName = userName; this.password = password; } /** * 对象被激活后,会进行调用 * * @param pooledObject a {@code PooledObject} wrapping the instance to be activated */ @Override public void activateObject(PooledObject<Connection> pooledObject) throws Exception { } /** * 销毁数据库连接 * * @param pooledObject a {@code PooledObject} wrapping the instance to be destroyed * @throws Exception 异常 */ @Override public void destroyObject(PooledObject<Connection> pooledObject) throws Exception { Connection connection = pooledObject.getObject(); connection.close(); } /** * 创建一个数据库连接 * * @return 数据库连接的池对象包装 * @throws Exception 异常 */ @Override public PooledObject<Connection> makeObject() throws Exception { Connection connection = DriverManager.getConnection(this.url, this.userName, this.password); return new DefaultPooledObject<>(connection); } /** * 回收资源时候进行调用 * @param pooledObject a {@code PooledObject} wrapping the instance to be passivated * * @throws Exception */ @Override public void passivateObject(PooledObject<Connection> pooledObject) throws Exception { } @Override @SneakyThrows public boolean validateObject(PooledObject<Connection> pooledObject) { Connection connection = pooledObject.getObject(); // 如果连接关闭说明已经失效就返回false告诉池子,已经失效,会自动移除 return !connection.isClosed(); } }

1|0连接池演示

@Test @DisplayName("验证回收对象") public void testReturn()throws Exception{ // 1. 构建一个数据连接池化工厂 String dbUrl = "jdbc:mysql://127.0.0.1:3306/test"; String user = "root"; String pass = "123456"; PooledConnectFactory pooledConnectFactory = new PooledConnectFactory(dbUrl, user, pass); // 2. 给池子添加支持的配置信息 GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<Connection>(); // 2.1 最大池化对象数量 config.setMaxTotal(5); // 2.2 最大空闲池化对象数量 config.setMaxIdle(2); // 2.3 最小空闲池化对象数量 config.setMinIdle(2); // 2.4 间隔多久检查一次池化对象状态,驱逐空闲对象,检查最小空闲数量小于就创建 config.setTimeBetweenEvictionRuns(Duration.ofSeconds(5)); // 2.5 阻塞就报错 config.setBlockWhenExhausted(true); // 2.6 最大等待时长超过5秒就报错,如果不配置一直进行等待 config.setMaxWait(Duration.ofSeconds(5)); // 2.7 是否开启jmx监控,默认开启 config.setJmxEnabled(true); // 2.8 一定要符合命名规则,否则无效 config.setJmxNameBase("org.apache.commons.pool2:type=MysqlConnObjectPool,name=ConnectJmxNameBase"); // 生成数据库连接池 // 连接池配置最大5个连接setMaxTotal(5),但是获取6次,那么有一次获取不到就会阻塞setBlockWhenExhausted(true), // 当等待了10秒setMaxWait(Duration.ofSeconds(10))还是获取不到。就直接报错 try (GenericObjectPool<Connection> connPool = new GenericObjectPool<>(pooledConnectFactory, config)) { for (int i = 1; i <= 7; i++) { Connection connection = connPool.borrowObject(); Statement statement = connection.createStatement(); ResultSet show_tables = statement.executeQuery("show tables"); printRows("Connect-" + i + ">", show_tables); connPool.returnObject(connection); } } }

参考:

  1. http://t.zoukankan.com/jinzhiming-p-5120459.html
  2. https://blog.csdn.net/LBWNB_Java/article/details/127034077
  3. https://blog.csdn.net/Message_lx/article/details/125896010

__EOF__

本文作者😎
本文链接https://www.cnblogs.com/dongye95/p/16768693.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   dongye95  阅读(1420)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示