此文是系列文章第十三篇,前几篇请点击链接查看
程序员的福音 - Apache Commons Compress
程序员的福音 - Apache Commons Collections
程序员的福音 - Apache Commons HttpClient
程序员的福音 - Apache Commons VFS(上)
程序员的福音 - Apache Commons VFS(下)
Apache Commons Pool 开源软件库提供了一个对象池 API 和许多对象池实现。
Pool 有两个版本,目前主流的是 Pool2,与 1.x 系列相比,Apache Commons Pool2 重新编写了对象池实现。除了性能和可伸缩性改进之外,版本2还包括健壮的实例跟踪和对象池监控。
后续文章出现的 Commons-Pool 指的就是 Pool2。
Commons-Net目前最新版本是2.9.0,最低要求Java8以上。
maven坐标如下:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
包结构如下:
org.apache.commons.pool2org.apache.commons.pool2.implorg.apache.commons.pool2.proxy
下面简单介绍一下其用法。
01. 简介
为什么要有对象池呢,假如一个对象创建耗时 500 毫秒,而我们调用它的方法仅耗时 10 毫秒,这种情况每次使用都 new 的话性价比很低,相当于每次都要耗费 550 毫秒。 对象池就是为了解决此类问题而诞生的,对于这些昂贵的对象来说,提前创建若干个对象用对象池管理起来,用的时候从对象池借来一个,用完后归还 可以大大提升性能。
对象池是一种享元模式的实现,常用于各种连接池的实现。 比如我们常见的数据库连接池 DBCP,Redis 客户端 Jedis 等都依赖 Commons-Pool。
Commons-Pool 主要有三个角色:
PooledObject:池化对象,用于包装实际的对象,提供一些附件的功能。如 Commons-Pool 自带的 DefaultPooledObject 会记录对象的创建时间,借用时间,归还时间,对象状态等,PooledSoftReference 使用 Java 的软引用来持有对象,便于 JVM 内存不够时回收对象。当然我们也可以实现 PooledObject 接口来定义我们自己的对象包装器。
PooledObjectFactory:对象工厂,ObjectPool 对于每个对象的核心操作会代理给 PooledObjectFactory。
需要一个新实例时,就调用 makeObject 方法。
需要借用对象时会调用 activateObject 方法激活对象,并且根据配置情况决定是否验证对象有效性,通过 validateObject 方法验证。
归还对象时会调用 passivateObject 方法钝化对象。
需要销毁对象时候调用 destroyObject 方法。
PooledObjectFactory 必须是线程安全的。
ObjectPool :对象池接口,用于管理池中的所有对象,对于每个对象的操作会代理给 ObjectFactory。ObjectPool 有多个实现,GenericObjectPool 提供了多种配置选项,包括限制空闲或活动实例的数量、在实例处于池中空闲时将其逐出等。从版本 2 开始,GenericObjectPool 还提供了废弃实例跟踪和删除功能。SoftReferenceObjectPool 可以根据需要增长,但允许垃圾收集器根据需要从池中逐出空闲实例。
以下是部分类图
图片 图片02. 使用方式
Commons-Pool 用起来很简单,下面我用一个例子简单介绍下其用法
首先我们创建一个对象用于测试,对象构造函数使用随机延迟模拟创建的复杂
/**
* 复杂的对象,创建出来比较耗时间
*/
public class ComplexObject {
private String name;
public ComplexObject(String name) {
try {
long t1 = System.currentTimeMillis();
// 模拟创建耗时操作
ThreadLocalRandom tlr = ThreadLocalRandom.current();
Thread.sleep(4000 + tlr.nextInt(2000));
long t2 = System.currentTimeMillis();
System.out.println(name + " 创建耗时: " + (t2-t1) + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
其次创建一个 ComplexPooledObjectFactory 实现。当我们有复杂的操作,比如激活对象,钝化对象,销毁对象(需要释放资源)等就需要实现 PooledObjectFactory 来定制,如果没有这些操作选择继承 BasePooledObjectFactory 抽象类更方便。下面分别给出两种创建的代码示例
1. 继承 BasePooledObjectFactory
public class SimplePooledObjectFactory extends BasePooledObjectFactory<ComplexObject> {
@Override
public ComplexObject create() {
// 随机指定一个名称,用于区分ComplexObject
String name = "test" + ThreadLocalRandom.current().nextInt(100);
return new ComplexObject(name);
}
@Override
public PooledObject<ComplexObject> wrap(ComplexObject obj) {
// 使用默认池化对象包装ComplexObject
return new DefaultPooledObject(obj);
}
}
2. 实现 PooledObjectFactory
public class ComplexPooledObjectFactory implements PooledObjectFactory<ComplexObject> {
@Override
public PooledObject<ComplexObject> makeObject() {
// 随机指定一个名称,用于区分ComplexObject并使用默认池化对象包装ComplexObject
String name = "test" + ThreadLocalRandom.current().nextInt(100);
return new DefaultPooledObject<>(new ComplexObject(name));
}
@Override
public void destroyObject(PooledObject<ComplexObject> p) {
// 销毁对象,当清空,空闲对象大于配置值等会销毁多余对象
// 此处应释放掉对象占用的资源,如关闭连接,关闭IO等
}
@Override
public boolean validateObject(PooledObject<ComplexObject> p) {
// 验证对象状态是否正常,是否可用
return true;
}
@Override
public void activateObject(PooledObject<ComplexObject> p) {
// 激活对象,使其可用
}
@Override
public void passivateObject(PooledObject<ComplexObject> p) {
// 钝化对象,使其不可用
}
}
最后编写测试代码
public static void main(String[] args) throws Exception {
// 创建配置对象
GenericObjectPoolConfig<ComplexObject> poolConfig = new GenericObjectPoolConfig<>();
// 最大空闲实例数,空闲超过此值将会被销毁淘汰
poolConfig.setMaxIdle(5);
// 最大对象数量,包含借出去的和空闲的
poolConfig.setMaxTotal(20);
// 最小空闲实例数,对象池将至少保留2个空闲对象
poolConfig.setMinIdle(2);
// 对象池满了,是否阻塞获取(false则借不到直接抛异常)
poolConfig.setBlockWhenExhausted(true);
// BlockWhenExhausted为true时生效,对象池满了阻塞获取超时,不设置则阻塞获取不超时,也可在borrowObject方法传递第二个参数指定本次的超时时间
poolConfig.setMaxWaitMillis(3000);
// 创建对象后是否验证对象,调用objectFactory#validateObject
poolConfig.setTestOnCreate(false);
// 借用对象后是否验证对象 validateObject
poolConfig.setTestOnBorrow(true);
// 归还对象后是否验证对象 validateObject
poolConfig.setTestOnReturn(true);
// 每30秒定时检查淘汰多余的对象, 启用单独的线程处理
poolConfig.setTimeBetweenEvictionRunsMillis(1000 * 60 * 30);
// 每30秒定时检查期间是否验证对象 validateObject
poolConfig.setTestWhileIdle(false);
// jmx监控,和springboot自带的jmx冲突,可以选择关闭此配置或关闭springboot的jmx配置
poolConfig.setJmxEnabled(false);
ComplexPooledObjectFactory objectFactory = new ComplexPooledObjectFactory();
GenericObjectPool<ComplexObject> objectPool = new GenericObjectPool<>(objectFactory, poolConfig);
// 申请对象
ComplexObject obj1 = objectPool.borrowObject();
println("第一次申请对象:" + obj1.getName());
// returnObject应该放在finally中 避免业务异常没有归还对象,demo仅做示例
objectPool.returnObject(obj1);
// 申请对象, 由于之前归还了,借用的还是之前的对象
ComplexObject obj2 = objectPool.borrowObject();
println("第二次申请对象:" + obj2.getName());
// 再次申请对象,由于之前没有归还,借用的是新创建的
ComplexObject obj3 = objectPool.borrowObject();
println("第三次申请对象:" + obj3.getName());
// returnObject应该放在finally中 避免业务异常没有归还对象,demo仅做示例
objectPool.returnObject(obj2);
objectPool.returnObject(obj3);
}
运行结果如下
test41 创建耗时: 5400ms
第一次申请对象:test41
第二次申请对象:test41
test58 创建耗时: 5349ms
第三次申请对象:test58
当然如果借用次数越多,节省下来的时间就越多。
由于示例比较简单粗暴,在对象池刚刚创建还没提前创建好对象,我们就去使用了,所以效果不是很理想,正常使用效果会比较好。
03. KeyedObjectPool
Commons-Pool 还有 KeyedPooledObjectFactory,KeyedObjectPool 接口,它支持 Key Value 形式。
public interface KeyedPooledObjectFactory<K, V> {
// 通过参数创建对象
PooledObject<V> makeObject(K key);
// 通过参数激活对象,使其可用
void activateObject(K key, PooledObject<V> obj);
// 通过参数钝化对象,使其不可用
void passivateObject(K key, PooledObject<V> obj);
// 通过参数验证对象状态是否正常,是否可用
boolean validateObject(K key, PooledObject<V> obj);
// 通过参数销毁对象,当清空,空闲对象大于配置值等会销毁多余对象
// 此处应释放掉对象占用的资源,如关闭连接,关闭IO等
void destroyObject(K key, PooledObject<V> obj);
}
具体使用方式就不做介绍了,用法和第二节的类似,区别是对象借用和归还操作需要额外传递自定义的 Key 参数。
04. 总结
Commons-Pool 作为对象池工具包,支持对象的管理、跟踪和监控,并且支持自定义池化对象来扩展对象管理的行为,如果有相关需求可以使用。
后续章节我将继续给大家介绍 commons 中其他好用的工具类库,期待你的关注。
网友评论