从apache-common-pool看如何写一个通用池
对象的创建和销毁在一定程度上会消耗系统的资源,虽然jvm的性能在近几年已经得到了很大的提高,对于多数对象来说,没有必要利用对象池技术来进行对象的创建和管理。但是对于有些对象来说,其创建的代价还是比较昂贵的,比如线程、tcp连接、数据库连接等对象。对于那些创建耗时较长,或者资源占用较多的对象,比如网络连接,线程之类的资源,通常使用池化来管理这些对象,从而达到提高性能的目的。
apache-common-pool提供了一个通用的对象池技术的实现。可以很方便的基于它来实现自己的对象池。比如DBCP和Jedis他们的内部对象池的实现就是依赖于apache-common-pool。
本文分析的是apache common pool2
组件分析
下面先来看看apache-common-pool的组成结构:
- PooledObjectFactory|KeyedPooledObjectFactory: 池化对象工厂,负责对PooledOjbect的创建,状态验证,销毁, 钝化,激活工作。
- PooledObject: 池化对象,这个就是前面所说需要池化的资源,被池化的对象可以抽离出共有属性,如,创建时间,状态,最近一次使用时间等。
- ObjectPool|KeyedObjectPool: 对象池,它是负责和对象使用者直接打交道的, 对使用者提供获取对象,返还对象接口。
池化对象工厂
PooledObjectFactory
PooledObjectFactory是一个池化对象工厂接口,定义了生成对象、激活对象、钝化对象、销毁对象的方法,其方法和继承关系如下图:
1 | public interface PooledObjectFactory<T> { |
从类图中可以看到 PooledObjectFactory有两个实现类:
BasePooledObjectFactory<T>
SynchronizedPooledObjectFactory<T>
BasePooledObjectFactory<T>
是有个抽象类,提供了两个抽象方法:
public abstract T create() throws Exception;
: 用来创建泛型实例public abstract PooledObject<T> wrap(T obj);
: 用来将泛型实例包装为池化对象
BasePooledObjectFactory<T>
的继承方法基本是空实现或默认实现,除了makeObject
方法。
1 |
|
下面来看看SynchronizedPooledObjectFactory<T>
,这是一个基于装饰器模式设计的同步对象工厂,可以看到内部存在两个属性,一个是可重入读写锁的写锁,一个是对象工厂的实例。所有继承方法都进行了锁的功能扩展,例如:
1 |
|
KeyedPooledObjectFactory
KeyedPooledObjectFactory 也是一个池化对象工厂接口,和 PooledObjectFactory 相比,特点是可以通过key来查找池化对象。
1 | public interface KeyedPooledObjectFactory<K, V> { |
从类图中可以看到 KeyedPooledObjectFactory 也有两个实现类:
BaseKeyedPooledObjectFactory<K, V>
SynchronizedKeyedPooledObjectFactory<K, V>
这两个实现类的特点和 PooledObjectFactory 的两个实现类类似,此处就不多说了。
生命周期
当通过对象工厂创建对象时:
- 每当需要新实例时,都会调用
makeObject()
。 - 在从池中借用之前已钝化的实例时都会调用
activateObject()
。 - 在激活的实例上调用
validateObject
,以确保它们可以从池中借用;在归还池的实例钝化之前调用validateObject
.validateObject
只会在已被激活的实例上使用。 - 当每个实例返回池时,会调用
passivateObject
。 - 当从池中“删除”时,会在每个实例上调用destroyObject
PooledObjectFactory必须是线程安全的。ObjectPool唯一的承诺是,对象的同一个实例不会一次传递给PoolableObjectFactory的多个方法。
池化对象
池化对象是对象池中对象的包装类,用于记录对象池需要的额外信息,例如状态,时间等。
PooledObject
1 | public interface PooledObject<T> extends Comparable<PooledObject<T>> { |
DefaultPooledObject
从上面的类图中我们可以看到 DefaultPooledObject实现了 PooledObject 接口。
1 | public class DefaultPooledObject<T> implements PooledObject<T> { |
DefaultPooledObject 被设计为线程安全的类。可以看到 属性上都有volatile修饰,涉及到修改属性的方法,在方法上都添加了对象级的 synchronize 锁。注意到有3个属性没有volatile修饰,其中createTime和 object 对于实例来说是不变的,一旦赋值,不会修改,所以不需要同步处理,state 则是在每处读写的地方都加了对象锁。PooledSoftReference 是对DefaultPooledObject 的扩展,用于包装软引用。
CallStack 用于记录和打印调用堆栈。
PooledObject 的状态流转
PooledObject 有下面几个状态,(在PooledObjectState中定义):
- IDLE: 空闲状态,在空闲队列中。
- ALLOCATED: 使用中
- EVICTION: 正在进行驱逐检查,在空闲队列中。
- EVICTION_RETURN_TO_HEAD: 正在进行驱逐检查时,试图分配该对象,从空闲队列中去除,由于正在检查,分配失败,当通过空闲检查后,重新放回到空闲队列头部。
- VALIDATION: 验证状态,在空闲队列中。
- VALIDATION_PREALLOCATED: 验证时试图分配,验证完后分配。
- VALIDATION_RETURN_TO_HEAD: 驱逐检查后正在验证,验证完后,放入空闲队列头部。
- INVALID: 无效,将会/已被销毁。
- ABANDONED: 抛弃状态,即将无效。
- RETURNING: 返回到池中。
在 pool-2包中,VALIDATION,VALIDATION_PREALLOCATED,VALIDATION_RETURN_TO_HEAD这3种状态未被使用。
对象池
ObjectPool
1 | public interface ObjectPool<T> extends Closeable { |
ObjectPool 是一个简单的对象池接口。下面是一个简单的使用示例:
1 | Object obj = null; |
ObjectPool有几个实现类。下面来看看它们:
ProxiedObjectPool
ProxiedObjectPool 实现了代理模式。
1 | public class ProxiedObjectPool<T> implements ObjectPool<T> { |
从源码可以看到,ProxiedObjectPool通过ProxySource实现对内部的ObjectPool的动态代理,可以给对象添加一些额外的信息,实现更好的控制。
包内还提供了ProxySource的JDK和cglib的实现,详细代码可以看JdkProxySource和CglibProxySource。
ErodingObjectPool
ErodingObjectPool 提供了一个基于时间和空闲对象数判断的动态收缩的对象池(漏池)。
1 | private final ObjectPool<T> pool; |
根据上面的源码,可以看到在向池中返回对象时,会进行判断,此时可能返回对象或把对象设为无效。而判断条件是传入的 factor 和 空闲对象数 numIdle。nextShrink的动态变化(可以在ErodingFactor的update方法中看到),从而实现对象池的动态收缩。
SynchronizedObjectPool
SynchronizedObjectPool 是基于装饰器模式设计的。通过内置的ReentrantReadWriteLock装饰了pool的各个方法,实现了具有读写锁的线程安全的对象池。
1 | private static final class SynchronizedObjectPool<T> implements ObjectPool<T> { |
SoftReferenceObjectPool
SoftReferenceObjectPool管理的对象的软引用,,SoftReferenceObjectPool是线程安全的。SoftReferenceObjectPool 继承自BaseObjectPool,BaseObjectPool是一个抽象类,方法大多是空实现或是空实现。下面我们来看看SoftReferenceObjectPool的内容。
1 | public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> { |
其他方法都较简单,这里就不赘述了。
GenericObjectPool详解
GenericObjectPool是最常用的对象池,继承自抽象类BaseGenericObjectPool,提供了一些可配置的对象池功能,GenericObjectPool被设计为线程安全。
config
配置 | 默认值 | 描述 |
---|---|---|
maxTotal | 8 | 池中最多可用的实例个数 |
maxIdle | 8 | 池中最大空闲的个数 |
minIdle | 0 | 池中最少空闲的个数 |
lifo | true | 是否 LIFO,后进先出 |
fairness | false | 等待线程拿空闲连接的方式,为true是先进先出的方式获取空闲对象 |
maxWaitMillis | -1 | 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常,单位:毫秒数.默认为-1时表示永不超时. |
evictorShutdownTimeoutMillis | 10L * 1000L |
驱逐者线程shutdown的等待时间 |
minEvictableIdleTimeMillis | 1000L * 60L * 30L |
对象空闲的最小时间,达到此值后空闲对象将可能会被移除,单位毫秒。负值表示不移除 |
softMinEvictableIdleTimeMillis | 1000L * 60L * 30L |
对象空闲的最小时间,达到此值后空闲对象将可能会被移除,但是会保留minIdle个空闲对象,单位毫秒。若minEvictableIdleTimeMillis为正数,那么该配置会被覆盖 |
numTestsPerEvictionRun | 3 | 对于驱逐者线程而言,每次检测的链接资源的个数。如果numTestsPerEvictionRun>=0,每次检查numTestsPerEvictionRun和空闲对象数的较小值,否则,返回Math.ceil(idleObjects.size() /Math.abs((double) numTestsPerEvictionRun)) |
evictionPolicy | null | 驱逐者线程驱逐策略,2.6版本提供 |
evictionPolicyClassName | DefaultEvictionPolicy.class.getName() |
驱逐者线程驱逐策略,2.6之前版本提供 |
testOnCreate | false | 创建对象时,是否使用validateObject验证 |
testOnBorrow | false | 借出对象时,是否使用validateObject验证 |
testOnReturn | false | 归还对象时,是否使用validateObject验证 |
testWhileIdle | false | 空闲对象在驱逐者线程检查后,是否使用validateObject验证 |
timeBetweenEvictionRunsMillis | -1L | 驱逐者检测线程检测的周期,毫秒数。如果为负值,表示不运行. |
blockWhenExhausted | true | 当池中active数量达到阀值时,是否阻塞borrowObject。 |
创建对象池
对象池的构造方法接受参数,对象工厂和对象池配置。在构造器中会初始化空闲对象容器idleObjects和所有对象容器 allObjects。此外,还可以设置AbandonedConfig,用于丢弃借出,但是长时间未使用的对象。
1 | private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects = |
对象租借
1 | public T borrowObject(final long borrowMaxWaitMillis) throws Exception { |
对象归还
1 | public void returnObject(final T obj) { |
evictor 检查
启动evictor入口
启动“空闲对象的驱逐者线程”的入口是:BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis
方法
1 | public final void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { |
启动
1 | final void startEvictor(final long delay) { |
驱逐者实现
1 | class Evictor implements Runnable { |
evictor方法
1 | public void evict() throws Exception { |
驱逐策略
在evict()方法中最后对象是否要被驱逐是调用了evictionPolicy.evict()的方法来判断的,commons-pool提供的驱逐策略如下:
1 | public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> { |
驱逐策略是支持自定义的,这里使用的是设计模式中的策略模式,我们只要实现EvictionPolicy接口,然后调用setEvictionPolicy()方法既可以更换驱逐策略.