为什么会有对象池
在实际的应用工程当中,存在一些被频繁使用的、创建或者销毁比较耗时、持有的资源也比较昂贵的一些对象。比如:数据库连接对象、线程对象。所以如果能够通过一种方式,把这类对象统一管理,让这类对象可以被循环利用的话,就可以减少很多系统开销(内存、CPU、IO等),极大的提升应用性能。
Apache Commons Pool
Apache Commons Pool就是一个对象池的框架,他提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。Apache Commons Pool是很多连接池实现的基础,比如DBCP连接池、Jedis连接池等。
Apache Commons Pool有个两个大版本,commons-pool和commons-pool2。commons-pool2是对commons-pool的重构,里面大部分核心逻辑实现都是完全重写的。我们所有的源码分析都是基于commons-pool2。
在commons-pool2中,对象池的核心接口叫做ObjectPool,他定义了对象池的应该实现的行为。
- addObject方法:往池中添加一个对象。池子里的所有对象都是通过这个方法进来的。
- borrowObject方法:从池中借走到一个对象。借走不等于删除。对象一直都属于池子,只是状态的变化。
- returnObject方法:把对象归还给对象池。归还不等于添加。对象一直都属于池子,只是状态的变化。
- invalidateObject:销毁一个对象。这个方法才会将对象从池子中删除,当然这其中最重要的就是释放对象本身持有的各种资源。
- getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。
- getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。
- clear:清理对象池。注意是清理不是清空,改方法要求的是,清理所有空闲对象,释放相关资源。
- close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。
在commons-pool2中,ObjectPool的核心实现类是GenericObjectPool。
本文讨论的就是addObject方法在GenericObjectPool中的实现。
在讨论具体实现之前,我们还有必要看一下该方法在ObjectPool接口的具体定义是如何描述的。
ObjectPool接口中addObject解析
/**
* Create an object using the {@link PooledObjectFactory factory} or other
* implementation dependent mechanism, passivate it, and then place it in
* the idle object pool. <code>addObject</code> is useful for "pre-loading"
* a pool with idle objects. (Optional operation).
*
* PooledObjectFactory是框架提供的一个对象工厂接口,他定了一些池中对象的特定操作方法。
* 如果要创建一个对象,可以实现PooledObjectFactory接口,按照common pool的约定来生产一个对象。
* 这只是common pool建议的实现方式。但创建对象不一定非要搞一个对象工厂出来,你也完全按自己意愿用任何形式实现。
* 只要你最终把对象放到池子里就可以,因为这才是addObject的核心要做的事情。
* 当然了文档里重点提到了idle object pool(空闲对象池)。意思是这个新对象要放到空闲对象池里。
* 个人觉得这其实已经是对实现者的一种干扰了,实现者是否需要把空闲对象、活跃对象分开存储那是实现者的事情。
* 这个addObject往往是在在给一个对象池预加载对象的时使用。
*
* @throws Exception
* when {@link PooledObjectFactory#makeObject} fails.
* 意思是,在这个方法的实现中,如果对象创建失败,抛出Exception异常(这里也是默认的和PooledObjectFactory绑定了,认为生产对象一定是通过PooledObjectFactory)
* @throws IllegalStateException
* after {@link #close} has been called on this pool.
* 意思是,在这个方法的实现中,如果发现已经调用了close方法关闭了对象池,那么应该抛出IllegalStateException异常。
* @throws UnsupportedOperationException
* when this pool cannot add new idle objects.
* 意思是,在这个方法的实现中,当对象池无法再添加新的空闲对象时,应该抛出UnsupportedOperationException异常。
*/
void addObject() throws Exception, IllegalStateException,
UnsupportedOperationException;
上面我们对addObject的接口定义进行一些解读,那么其中有一个点值得重点关注,就是PooledObjectFactory,这是一个对象工厂(我们先不对其进行具体的分析),ObjectPool接口是非常倾向、或者说如果作为common pool框架的开发者,那么就应该通过实现PooledObjectFactory的makeObject来进行对象的创建。那么我们接下就看看,ObjectPool的实现类GenericObjectPool是如何实现addObject方法的。
GenericObjectPool中addObject解析
为了便于阅读,只截取了和我们讨论分析相关的重点代码,并且做了一些顺序的调整。我们先对一些必要属性进行说明和解析,然后再看具体方法的逻辑。
public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {
/*
* All of the objects currently associated with this pool in any state. It
* excludes objects that have been destroyed. The size of
* {@link #allObjects} will always be less than or equal to {@link
* #_maxActive}. Map keys are pooled objects, values are the PooledObject
* wrappers used internally by the pool.
*/
/*
* allObjects 是一个ConcurrentHashMap,里面存储的就是对象池中所有的对象,ConcurrentHashMap能够保证并发读写安全。
* 这里面不包括已经被销毁的对象,也就是说被销毁的对象会从allObjects中被移除掉。allObjects的数量应该小于等于maxActive(最大活跃对象数量)的值。
* ConcurrentHashMap的key是IdentityWrapper类型的对象,他可以包装任何对象T,在这里他包装的T的实例就是业务上产生的最直接的对象(数据库连接对象、线程对象等)。
* ConcurrentHashMap的value是PooledObject类型的对象,他持有的T的实例也是业务上产生的最直接的对象(数据库连接对象、线程对象等)。
* 关于IdentityWrapper和PooledObject这两个类(接口)的作用,我们下面结合代码会再详细讲。
*/
private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects =
new ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>();
/*
* The combined count of the currently created objects and those in the
* process of being created. Under load, it may exceed {@link #_maxActive}
* if multiple threads try and create a new object at the same time but
* {@link #create()} will ensure that there are never more than
* {@link #_maxActive} objects created at any one time.
*/
/*
* createCount 用来统计对象池当前一共创建了多少对象。这个值在多线程并发调用create方法时可能会超过maxActive的值的。
* 但是create方法会有相应的防护机制,能够确保对象数量绝对不够超过maxActive。(这个我们在create方法解析中会看到是如何处理的)
* 他的作用就是防止对象池中的对象数量超过最大限制。
*/
private final AtomicLong createCount = new AtomicLong(0);
/*
* 用一个链表结构的阻塞队列来存储空闲对象
*/
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
/*
* 对象工厂,GenericObjectPool的实现依赖了PooledObjectFactory来生产对象
*/
private final PooledObjectFactory<T> factory;
/**
* Create an object, and place it into the pool. addObject() is useful for
* "pre-loading" a pool with idle objects.
* <p>
* If there is no capacity available to add to the pool, this is a no-op
* (no exception, no impact to the pool). </p>
*/
/**
* 创建一个对象,放到对象池里。在对象池初始化预加载空闲对象时调用
* 如果对象池里放不下了,那么该方法不会抛出异常,也不会对对象池有任何影响。
*/
@Override
public void addObject() throws Exception {
assertOpen(); // 校验连接池是否是open状态,实际上就是看看是不是非close状态,如果关闭了就不允许再添加。
if (factory == null) { // 如果对象工厂为空,抛出异常。
throw new IllegalStateException(
"Cannot add objects without a factory.");
}
// 此处调用了create方法来创建一个新的对象,所以这个create方法是一个比较核心的方法
PooledObject<T> p = create();
addIdleObject(p); // 把这个新建好的对象,放到空闲对象队列中。
}
/**
* Attempts to create a new wrapped pooled object.
* <p>
* If there are {@link #getMaxTotal()} objects already in circulation
* or in process of being created, this method returns null.
*
* @return The new wrapped pooled object
*
* @throws Exception if the object factory's {@code makeObject} fails
*/
/**
* 尝试创建一个包装对象(最原始的对象应该是:数据库连接、线程对象等),
* 此处的PooledObject<T>中的T指的就是我们的原始对象类型,我们的原始对象也被包装在了PooledObject对象中。
*
* 如果对象池中对象已经满了,或者正在创建中,这个方法返回null
*
* 如果factory的makeObject方法发生异常,则抛出
*/
private PooledObject<T> create() throws Exception {
int localMaxTotal = getMaxTotal(); // 获取对象池的对象数量限制
long newCreateCount = createCount.incrementAndGet();// createCount先自增
/*
maxTotal的默认值是-1,意味着不限制对象池中的对象数量,如果设置了一个大于-1的值,意味着配置了限制
如果newCreateCount已经大于了maxTotal,或者大于了Integer的最大值,
那么认为对象数量已经超出了限制,则返回null,同时createCount自减来保证自己存储的值和对象的数量是对等的。
*/
if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||
newCreateCount > Integer.MAX_VALUE) {
createCount.decrementAndGet();
return null;
}
// PooledObject是一个接口,common-pool为其提供了一个默认实现类DefaultPooledObject,可以满足绝大多数需求
// 但是使用方也是完全可以自己实现PooledObject接口,在自己的factory中使用自己的PooledObject接口实现类。
final PooledObject<T> p;
try {
/*
调用PooledObjectFactory接口的某个实现类的实例factory来创建一个对象。
如果我们要想用GenericObjectPool来实现我们自己应用上的一个对象池,那么就必须提供一个PooledObjectFactory的实现类,
并注入到GenericObjectPool的实例中。
例如:Jedis使用GenericObjectPool实现自己的redis连接池,他对于PooledObjectFactory的实现类是redis.clients.jedis.JedisFactory
*/
p = factory.makeObject(); // 调用factory的makeObject返回一个PooledObject对象实例,这个对象里面包装了我们需要的真正的原始对象。
} catch (Exception e) {
createCount.decrementAndGet(); // 如果产生异常,计数自减。
throw e; // 向外抛出(这就对应上了方法注释上的描述)
}
// abandonedConfig是针对遗弃对象管理相关的配置
AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getLogAbandoned()) { // 如果配置了abandonedConfig
p.setLogAbandoned(true); // 调用PooledObject对象的setLogAbandoned方法,可以针对这个对象进行使用情况追踪。
}
// 关于AbandonedConfig,请单独参考我的另外一篇解析:https://blog.csdn.net/weixin_42340670/article/details/107136994
/*
注意这个变量是createdCount,不是createCount。
createCount
是定义在本类内部,是本类的一个实例变量,
用来统计对象池中有多少对象,
在destory方法中会调用createCount.decrementAndGet().
createdCount
定义在父类BaseGenericObjectPool类中,
他的作用是统计对象池一共创建多多少对象,
没有任何地方会调用createdCount.decrementAndGet(),
所以他是只增不减的。
*/
createdCount.incrementAndGet(); // 只要成功的创建一个对象,那么createdCount就加1,即便对象被回收了,那么也不会影响createdCount。
/*
新的对象创建好了,需要把他添加到池子里。在GenericObjectPool中这个池子的容器allObjects用的一个ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>结构。
key之所以用IdentityWrapper把原始对象包装起来,主要是因为原始对象是和使用方强耦合的业务对象,而且,封装了原始对象的PooledObject的实现类也有可能是使用方自定义实现的。
既然原始对象类、PooledObject的实现类都(可能)是外部输入类,那么就有可能重写了hashcode和equals方法。直接放到Map里,就可能产生覆盖。
所以key用IdentityWrapper包装了一下,IdentityWrapper内部还是基于原始对象T的内存地址作为算子来重新生成hashcode,保证了即使两个不同的T对象的hashcode相同,在容器中也肯定不会产生覆盖。
同时因为IdentityWrapper的hashcode的算子完全基于T实例的内存地址生成,那么随便new一个IdentityWrapper的实例,只要你包装了T1,都可以作为key获取到包装了T1的PoolObject<T>。
*/
allObjects.put(new IdentityWrapper<T>(p.getObject()), p);
return p;
}
/**
* Add the provided wrapped pooled object to the set of idle objects for
* this pool. The object must already be part of the pool. If {@code p}
* is null, this is a no-op (no exception, but no impact on the pool).
*
* @param p The object to make idle
*
* @throws Exception If the factory fails to passivate the object
*/
/**
* 把一个PooledObject的实例对象添加到空闲对象集合中。
* 要添加的对象一定是保证已经在对象池(allObjects)里了。如果p为null,那么不会pa
* 如果p为null,那么不会抛出异常,也不会对对象池产生任何影响
*
* 如果产生异常的话,那就应该是passivateObject方法执行失败
*/
private void addIdleObject(PooledObject<T> p) throws Exception {
if (p != null) {
/*
passivateObject方法按照PooledObjectFactory接口中对其职责的描述应该叫做:对一个对象进行反初始化,什么意思?
抛开对象的创建流程先不谈,一个对象变为空闲,有可能是曾经被使用过,但是长时间没有被再使用,这个时候他本身除了持有很多核心资源外,可能还挂载了其他一些附加轻量的资源或者属性。
对象池的目的就是为了避免重复的创建这种持有昂贵资源的对象,所以即使是对象状态变为空闲,但是本身的核心资源也不能被释放。
但是那些外围的轻量的资源和属性是可以被卸载和重置的。所以可以这么理解,passivateObject方法是在对象变为空闲时,重置或者卸载那些非核心的资源和属性的。
那怎么区分核心和非核心呢? 这个事情是使用方要操心的事情,你如果觉得你的对象持有的都是核心资源,passivateObject你还必须实现,所以你可以再方法内部啥逻辑都不写。其实JedisFactory就是这么干的。
和passivateObject对应的是activateObject方法,activateObject意思是一个对象在被调用方使用前重新初始化一些附加信息。
JedisFactory对于activateObject提供了实现,内部逻辑做了redis dbindex的修正动作。
*/
factory.passivateObject(p);
if (getLifo()) { // 获取对象池定义的优先级策略
idleObjects.addFirst(p); // 如果是后进先出,那么就把对象添加到队列的第一个位置。
} else {
idleObjects.addLast(p); // 如果是先进先出,那么就把对象添加到队列的最后一个位置。
}
}
}
}
通过上面的解析,我们能够知道GenericObjectPool定义好了往对象池添加对象的核心流程。而对象从哪里来,如何产生的,是需要使用方去实现PooledObjectFactory接口定义自己的对象工厂来实现的。所以任何一个基于GenericObjectPool来实现自己对象池的框架、或者应用程序最核心的就是编写自己的PooledObjectFactory实现类。