apache common pool2 对象池使用和原理
简介
apache common pool2 对象池是管理管理 Java 资源对象的工具,主要功能是可以将对象缓存起来,当首次使用时创建,用完返回,下次使用时可以直接取用而不必重新创建。可以用于数据库连接,Socket 等资源的管理中。
使用示例
引入 maven 依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
测试代码
MyObject :自定义的资源类
public class MyObject {
LocalTime time;
public String id;
int seq = 0;
public MyObject(String id){
this.id = id;
time = LocalTime.now();
}
public void cry(){
System.out.println(time + "cry "+ seq++);
this.hashCode();
}
public boolean isOk(){
return this.seq < 2;
}
}
MyKey :自定义的资源对象的 key,用于区分资源类
public class MyKey {
public String key ;
public MyKey(String key){
this.key = key;
}
@Override
public int hashCode(){
return (this.key).hashCode();
}
}
MyPooledObjectFactory : 自定义 Factory 类,实现了 apache 的接口,用于管理资源类
public class MyPooledObjectFactory implements KeyedPooledObjectFactory<MyKey ,MyObject> {
@Override
public void activateObject(MyKey s, PooledObject<MyObject> pooledObject) throws Exception {
System.out.println("激活对象,借用时调用");
}
@Override
public void destroyObject(MyKey s, PooledObject<MyObject> pooledObject) throws Exception {
System.out.println("销毁对象");
}
@Override
public PooledObject<MyObject> makeObject(MyKey s) throws Exception {
System.out.println("创建对象");
MyObject object = new MyObject(s.key);
return new DefaultPooledObject(object);
}
@Override
public void passivateObject(MyKey s, PooledObject<MyObject> pooledObject) throws Exception {
System.out.println("钝化对象,归还时调用");
}
@Override
public boolean validateObject(MyKey s, PooledObject<MyObject> pooledObject) {
System.out.println("判断对象是否可用");
return pooledObject.getObject().isOk();
}
}
MyObjectPool : 封装一个对象池工具并配置对象池
public class MyObjectPool {
@Getter
private final GenericKeyedObjectPool<MyKey,MyObject> pool;
public MyObjectPool() {
GenericKeyedObjectPoolConfig<MyObject> config = new GenericKeyedObjectPoolConfig<>();
// 最大资源数
config.setMaxTotal(1);
// 每个 key 对应的最大资源数
config.setMaxTotalPerKey(1);
// 最大 key 数量
config.setMaxIdlePerKey(1);
// 没有可用资源时是否要等待
config.setBlockWhenExhausted(true);
// 创建新资源对象后是否立即校验是否可用
config.setTestOnCreate(false);
// 借用资源对象前是否校验是否可用
config.setTestOnBorrow(true);
KeyedPooledObjectFactory<MyKey , MyObject> factory = new MyPooledObjectFactory();
this.pool = new GenericKeyedObjectPool<>(factory, config);
}
}
测试
public static void main(String[] args) throws Exception {
MyObjectPool myObjectPool = new MyObjectPool();
GenericKeyedObjectPool<MyKey , MyObject> pool = myObjectPool.getPool();
MyKey key = new MyKey("1");
for(int i=0;i<3;i++){
MyObject myObject = pool.borrowObject(key);
myObject.cry();
pool.returnObject(key,myObject);
System.out.println(" ");
}
}
输出
创建对象
激活对象,借用时调用
判断对象是否可用
16:47:23.502cry 0
钝化对象,归还时调用
激活对象,借用时调用
判断对象是否可用
16:47:23.502cry 1
钝化对象,归还时调用
激活对象,借用时调用
判断对象是否可用
销毁对象
创建对象
激活对象,借用时调用
判断对象是否可用
16:47:23.529cry 0
钝化对象,归还时调用
代码解释
上面测试代码使用的是 apache pool 中带有 key 的对象池,还有一种不需要使用 key 的对象池
首先定义了自己的资源类和 key 类
自定义一个 Factory 类,实现了 KeyedPooledObjectFactory 接口,注意泛型中分别是自定义的 key 类和资源类。工厂类方法中定义了几个方法,分别对应资源类的各个生命周期,在这里写自定义的创建、销毁等逻辑。
封装对象池工具,初始化一个 GenericKeyedObjectPoolConfig 配置类,定义了对象池的各种参数。新建一个 GenericKeyedObjectPool 类,入参是自定义的工厂类实例和配置类。
使用时,直接调用 GenericKeyedObjectPool 的 borrowObject 方法借用资源对象,日志可以看出,首次借用时会新建对象,之后再次使用时不用新建。根据自己的配置,在创建或者借用时会调用校验方法校验对象是否可以,如果不可用会调用工厂类的 destroyObject 方法销毁对象,然后新建对象。用完对象后,调用 GenericKeyedObjectPool 的 returnObject 方法归还对象,以便下次使用。
原理
从 GenericKeyedObjectPool 的 borrowObject 方法进入源码查看:
@Override
public T borrowObject(final K key) throws Exception {
return borrowObject(key, getMaxWaitDuration().toMillis());
}
此方法调用了另一个带有超时时间参数的方法
/**
* Borrows an object from the sub-pool associated with the given key using
* the specified waiting time which only applies if
* {@link #getBlockWhenExhausted()} is true.
* <p>
* If there is one or more idle instances available in the sub-pool
* associated with the given key, then an idle instance will be selected
* based on the value of {@link #getLifo()}, activated and returned. If
* activation fails, or {@link #getTestOnBorrow() testOnBorrow} is set to
* {@code true} and validation fails, the instance is destroyed and the
* next available instance is examined. This continues until either a valid
* instance is returned or there are no more idle instances available.
* </p>
* <p>
* If there are no idle instances available in the sub-pool associated with
* the given key, behavior depends on the {@link #getMaxTotalPerKey()
* maxTotalPerKey}, {@link #getMaxTotal() maxTotal}, and (if applicable)
* {@link #getBlockWhenExhausted()} and the value passed in to the
* {@code borrowMaxWaitMillis} parameter. If the number of instances checked
* out from the sub-pool under the given key is less than
* {@code maxTotalPerKey} and the total number of instances in
* circulation (under all keys) is less than {@code maxTotal}, a new
* instance is created, activated and (if applicable) validated and returned
* to the caller. If validation fails, a {@code NoSuchElementException}
* will be thrown.
* </p>
* <p>
* If the associated sub-pool is exhausted (no available idle instances and
* no capacity to create new ones), this method will either block
* ({@link #getBlockWhenExhausted()} is true) or throw a
* {@code NoSuchElementException}
* ({@link #getBlockWhenExhausted()} is false).
* The length of time that this method will block when
* {@link #getBlockWhenExhausted()} is true is determined by the value
* passed in to the {@code borrowMaxWait} parameter.
* </p>
* <p>
* When {@code maxTotal} is set to a positive value and this method is
* invoked when at the limit with no idle instances available under the requested
* key, an attempt is made to create room by clearing the oldest 15% of the
* elements from the keyed sub-pools.
* </p>
* <p>
* When the pool is exhausted, multiple calling threads may be
* simultaneously blocked waiting for instances to become available. A
* "fairness" algorithm has been implemented to ensure that threads receive
* available instances in request arrival order.
* </p>
*
* @param key pool key
* @param borrowMaxWaitMillis The time to wait in milliseconds for an object
* to become available
*
* @return object instance from the keyed pool
*
* @throws NoSuchElementException if a keyed object instance cannot be
* returned because the pool is exhausted.
*
* @throws Exception if a keyed object instance cannot be returned due to an
* error
*/
public T borrowObject(final K key, final long borrowMaxWaitMillis) throws Exception {
assertOpen();
final AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) &&
(getNumActive() > getMaxTotal() - 3)) {
removeAbandoned(ac);
}
PooledObject<T> p = null;
// Get local copy of current config so it is consistent for entire
// method execution
final boolean blockWhenExhausted = getBlockWhenExhausted();
boolean create;
final long waitTimeMillis = System.currentTimeMillis();
final ObjectDeque<T> objectDeque = register(key);
try {
while (p == null) {
create = false;
p = objectDeque.getIdleObjects().pollFirst();
if (p == null) {
p = create(key);
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) {
if (p == null) {
if (borrowMaxWaitMillis < 0) {
p = objectDeque.getIdleObjects().takeFirst();
} else {
p = objectDeque.getIdleObjects().pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(appendStats(
"Timeout waiting for idle object, borrowMaxWaitMillis=" + borrowMaxWaitMillis));
}
} else if (p == null) {
throw new NoSuchElementException(appendStats("Pool exhausted"));
}
if (!p.allocate()) {
p = null;
}
if (p != null) {
try {
factory.activateObject(key, p);
} catch (final Exception e) {
try {
destroy(key, p, true, DestroyMode.NORMAL);
} catch (final Exception e1) {
// Ignore - activation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(appendStats("Unable to activate object"));
nsee.initCause(e);
throw nsee;
}
}
if (p != null && getTestOnBorrow()) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(key, p);
} catch (final Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
if (!validate) {
try {
destroy(key, p, true, DestroyMode.NORMAL);
destroyedByBorrowValidationCount.incrementAndGet();
} catch (final Exception e) {
// Ignore - validation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
appendStats("Unable to validate object"));
nsee.initCause(validationThrowable);
throw nsee;
}
}
}
}
}
} finally {
deregister(key);
}
updateStatsBorrow(p, Duration.ofMillis(System.currentTimeMillis() - waitTimeMillis));
return p.getObject();
}
先解释一下注释:
从子资源池中根据 key 拿到对象,当 getBlockWhenExhausted() 方法返回 true 时,如果没有对象则会等待参数给定的时间
当根据key找到资源对象时会根据 getLifo() 返回的方法决定以何种顺序取出资源,并根据配置检测对象是否可用,如果不可用就销毁,并取下一个,一直到取出可用对象或者没有可用对象
如果没有可用对象,就根据配置的最大对象数量信息,决定是否新建对象并返回。
如果根据 key 没有找到对象而且容量满了,就销毁一部分,然后创建新资源
在看具体代码:
先根据 AbandonedConfig 把长时间不用的资源丢掉
根据 key 拿到资源队列
从队列中拿资源,拿到的是 null 就创建一个新的
如果创建失败但设置了可以等待,则等带一段时间,等待别的线程用完归还资源
如果还获取不到就抛出异常
能拿到就分配资源:改变资源状态,记录资源借用时间等
返回资源