详解commons-pool2池化技术
⽬录
⼀、前⾔
⼆、commons-pool2池化技术剖析
2.1、核⼼三元素
2.1.1、ObjectPool
2.1.2、PooledObjectFactory
2.1.3、PooledObject
2.2、对象池逻辑分析
2.2.1、对象池接⼝说明
2.2.2、对象创建解耦
2.2.3、对象池源码分析
2.3、核⼼业务流程
2.3.1、池化对象状态变更
2.3.2、对象池browObject过程
2.3.3、对象池returnObject的过程执⾏逻辑
2.4、拓展和思考
2.4.1、关于LinkedBlockingDeque的另种实现
2.4.2、对象池的⾃我保护机制
三、写在最后
⼀、前⾔
我们经常会接触各种池化的技术或者概念,包括对象池、连接池、线程池等,池化技术最⼤的好处就
是实现对象的重复利⽤,尤其是创建和使⽤⼤对象或者宝贵资源(HTTP连接对象,MySQL连接对象)等⽅⾯的时候能够⼤⼤节省系统开销,对提升系统整体性能也⾄关重要。
在并发请求下,如果需要同时为⼏百个query操作创建/关闭MySQL的连接或者是为每⼀个HTTP请求创建⼀个处理线程或者是为每⼀个图⽚或者XML解析创建⼀个解析对象⽽不使⽤池化技术,将会给系统带来极⼤的负载挑战。
⼆、commons-pool2池化技术剖析
越来越多的框架在选择使⽤apache commons-pool2进⾏池化的管理,如jedis-cluster,commons-pool2⼯作的逻辑如下图所⽰:
2.1、核⼼三元素
2.1.1、ObjectPool
对象池,负责对对象进⾏⽣命周期的管理,并提供了对对象池中活跃对象和空闲对象统计的功能。
2.1.2、PooledObjectFactory
对象⼯⼚类,负责具体对象的创建、初始化,对象状态的销毁和验证。commons-pool2框架本⾝提供了默认的抽象实现BaPooledObjectFactory ,业务⽅在使⽤的时候只需要继承该类,然后实现warp和create⽅法即可。
2.1.3、PooledObject
池化对象,是需要放到ObjectPool对象的⼀个包装类。添加了⼀些附加的信息,⽐如说状态信息,创建时间,激活时间等。commons-pool2提供了DefaultPooledObject和 PoolSoftedObject 2种实现。其中PoolSoftedObject继承⾃DefaultPooledObject,不同点是使⽤SoftReference 实现了对象的软引⽤。获取对象的时候使⽤也是通过SoftReference进⾏获取。
2.2、对象池逻辑分析
2.2.1、对象池接⼝说明
1)我们在使⽤commons-pool2的时候,应⽤程序获取或释放对象的操作都是基于对象池进⾏的,对象池核⼼接⼝主要包括如下:
/**
*向对象池中增加对象实例
*/
void addObject() throws Exception, IllegalStateException,
UnsupportedOperationException;
/**
* 从对象池中获取对象
*/
T borrowObject() throws Exception, NoSuchElementException,
IllegalStateException;
/**
* 失效⾮法的对象
*/
void invalidateObject(T obj) throws Exception;
/**
* 释放对象⾄对象池
*/
void returnObject(T obj) throws Exception;
除了接⼝本⾝之外,对象池还⽀持对对象的最⼤数量,保留时间等等进⾏设置。对象池的核⼼参数项包括
maxTotal,maxIdle,minIdle,maxWaitMillis,testOnBorrow 等。
2.2.2、对象创建解耦
对象⼯⼚是commons-pool2框架中⽤于⽣成对象的核⼼环节,业务⽅在使⽤过程中需要⾃⼰去实现对应的对象⼯⼚实现类,通过⼯⼚模式,实现了对象池与对象的⽣成与实现过程细节的解耦,每⼀个对象池应该都有对象⼯⼚的成员变量,如此实现对象池本⾝和对象的⽣成逻辑解耦。
可以通过代码进⼀步验证我们的思路:
public GenericObjectPool(final PooledObjectFactory<T> factory) {
this(factory, new GenericObjectPoolConfig<T>());
}
public GenericObjectPool(final PooledObjectFactory<T> factory,
final GenericObjectPoolConfig<T> config) {
super(config, ONAME_BASE, JmxNamePrefix());
if (factory == null) {
jmxUnregister(); // tidy up
throw new IllegalArgumentException("factory may not be null");
}
this.factory = factory;
idleObjects = new LinkedBlockingDeque<>(Fairness());
tConfig(config);
}
public GenericObjectPool(final PooledObjectFactory<T> factory,
final GenericObjectPoolConfig<T> config, final AbandonedConfig abandonedConfig) {
this(factory, config);
tAbandonedConfig(abandonedConfig);
}
可以看到对象池的构造⽅法,都依赖于对象构造⼯⼚PooledObjectFactory,在⽣成对象的时候,基于对象池中定义的参数和对象构造⼯⼚来⽣成。
/**
* 向对象池中增加对象,⼀般在预加载的时候会使⽤该功能
*/
@Override
public void addObject() throws Exception {
asrtOpen();
if (factory == null) {
throw new IllegalStateException(
"Cannot add objects without a factory.");
}
final PooledObject<T> p = create();
addIdleObject(p);
}
create() ⽅法基于对象⼯⼚来⽣成的对象,继续往下跟进代码来确认逻辑;
final PooledObject<T> p;
try {
p = factory.makeObject();
if (getTestOnCreate() && !factory.validateObject(p)) {
createCount.decrementAndGet();
return null;
}
} catch (final Throwable e) {
createCount.decrementAndGet();
throw e;
} finally {
synchronized (makeObjectCountLock) {
makeObjectCount--;
}
}
此处确认了factory.makeObject()的操作,也印证了上述的推测,基于对象⼯⼚来⽣成对应的对象。
为了更好的能够实现对象池中对象的使⽤以及跟踪对象的状态,commons-pool2框架中使⽤了池化对象PooledObject的概念,PooledObject 本⾝是泛型类,并提供了getObject()获取实际对象的⽅法。
2.2.3、对象池源码分析
经过上述分析我们知道了对象池承载了对象的⽣命周期的管理,包括整个对象池中对象数量的控制等逻辑,接下来我们通过GenericObjectPool的源码来分析究竟是如何实现的。
对象池中使⽤了双端队列LinkedBlockingDeque来存储对象,LinkedBlockingDeque对列⽀持FIFO和FILO两种策略,基于AQS来实现队列的操作的协同。
LinkedBlockingDeque提供了队尾和队头的插⼊和移除元素的操作,相关操作都进⾏了加⼊重⼊锁的
加锁操作队列中设置notFull 和 notEmpty 两个状态变量,当对队列进⾏元素的操作的时候会触发对应的执⾏await和notify等操作。
/**
* 第⼀个节点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
private transient Node<E> first; // @GuardedBy("lock")
/**
* 最后⼀个节点
* Invariant: (first == null && last == null) ||
* ( == null && last.item != null)
*/
private transient Node<E> last; // @GuardedBy("lock")
/** 当前队列长度 */
private transient int count; // @GuardedBy("lock")
/** 队列最⼤容量 */
private final int capacity;
/** 主锁 */
private final InterruptibleReentrantLock lock;
/** 队列是否为空状态锁 */
private final Condition notEmpty;
/** 队列是否满状态锁 */
private final Condition notFull;
队列核⼼点为:
1.队列中所有的移⼊元素、移出、初始化构造元素都是基于主锁进⾏加锁操作。
2.队列的offer和pull⽀持设置超时时间参数,主要是通过两个状态Condition来进⾏协调操作。如在进⾏offer操作的时候,如果操作不成功,则基于notFull状态对象进⾏等待。
public boolean offerFirst(final E e, final long timeout, final TimeUnit unit)
throws InterruptedException {
long nanos = Nanos(timeout);
lock.lockInterruptibly();
try {
while (!linkFirst(e)) {
if (nanos <= 0) {
return fal;
}
nanos = notFull.awaitNanos(nanos);
}
return true;
} finally {
lock.unlock();
}
}
如进⾏pull操作的时候,如果操作不成功,则对notEmpty进⾏等待操作。
public E takeFirst() throws InterruptedException {
lock.lock();
try {
E x;
while ( (x = unlinkFirst()) == null) {
notEmpty.await();
}
return x;
} finally {
lock.unlock();
}
}
反之当操作成功的时候,则进⾏唤醒操作,如下所⽰:
private boolean linkLast(final E e) {
// asrt lock.isHeldByCurrentThread();
if (count >= capacity) {
return fal;
}
final Node<E> l = last;
final Node<E> x = new Node<>(e, l, null);
last = x;
if (first == null) {
first = x;
} el {
< = x;
}
++count;
notEmpty.signal();
return true;
}
2.3、核⼼业务流程
2.3.1、池化对象状态变更
上图是PooledObject的状态机图,蓝⾊表⽰状态,红⾊表⽰与ObjectPool相关的⽅法.PooledObject的
状态为:IDLE、ALLOCATED、RETURNING、ABANDONED、INVALID、EVICTION、EVICTION_RETURN_TO_HEAD
所有状态是在PooledObjectState类中定义的,其中⼀些是暂时未使⽤的,此处不再赘述。
2.3.2、对象池browObject过程
第⼀步、根据配置确定是否要为标签删除调⽤removeAbandoned⽅法。
第⼆步、尝试获取或创建⼀个对象,源码过程如下:
//1、尝试从双端队列中获取对象,pollFirst⽅法是⾮阻塞⽅法
p = idleObjects.pollFirst();
if (p == null) {
p = create();
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) {
if (p == null) {
if (borrowMaxWaitMillis < 0) {
//2、没有设置最⼤阻塞等待时间,则⽆限等待
p = idleObjects.takeFirst();
} el {
//3、设置最⼤等待时间了,则阻塞等待指定的时间
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
}
}
}
⽰意图如下所⽰: