`
pangwu86
  • 浏览: 115745 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Apache Commons-Pool 源码分析

 
阅读更多

 

Commons-Pool

首先看下两个重要的类结构:

 

ObjectPool defines a simple pooling interface.

  • GenericObjectPool: ObjectPool implementation with configurable LIFO/FIFO behavior. The default behavior is for the pool to act as a LIFO queue. What this means is that when there are idle objects available in the pool, borrowObject returns the most recently returned ("last in") instance. If the lifo the property of the pool false, instances are returned in the oppposite order - first-in, first-out.
  • StackObjectPool: ObjectPool implementation with a LIFO (Last In First Out) behavior.
  • SoftReferenceObjectPool: ObjectPool implementation with a LIFO (Last In First Out) behavior. Additionally this pool wraps each object in a SoftReference allowing the garbage collector to remove them in response to memory demand.

 

ObjectPool 定义了池相关操作的接口

 

GenericObjectPool 提供了后进先出(LIFO)与先进先出(FIFO)两种行为模式的池。默认情况是采用后进先出,即当有空闲对象时,调用borrowObject方法,返回最近时刻放进去的那个实例对象。这个行为是通过lifo这个属性控制的。

 

StackObjectPool 采用后进先出行为模式的池。(栈的特点,学过数据结构的都懂的)

 

SoftReferenceObjectPool 采用后进先出行为模式的池。池内对象被SoftReference包裹,允许垃圾回收器在有内存需要的时候删除这部分对象。

 

除了上述信息外,如果你查看过三个实现类的源码,会发现下面这些特点:

 

GenericObjectPool 采用的是org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。而在新版本中(至少1.5.6已经是了),CursorableLinkedList这里类被拷贝到了org.apache.commons.pool.impl包下, 也就是说是用pool组件不再需要依赖commons-collections组件了。

 

StackObjectPool 采用的是java.util.Stack对象来保存对象池里的对象。

 

SoftReferenceObjectPool 采用的是java.util.ArrayList对象来保存对象池里的对象。关于这里的软引用问题,不做深入解释,大家只要明白当内存不足的时候,池中的对象可以被Java虚拟机回收。

 

GenericObjectPool 是三个实现中最复杂的,每次调用borrowObjectreturnObject,度会涉及空闲对象分配,多线程等问题。分析源码时,推荐先从StackObjectPool看起,最后再分析GenericObjectPool

 

 

 

KeyedObjectPool pools instances of multiple types. Each type may be accessed using an arbitrary key.

 

KeyedObjectPool 包含多种类型的池,采用K-V方式,每个类型都可以通过特定的Key获取。

 

GenericKeyedObjectPool采用先进先出行为模式的池。

 

StackKeyedObjectPool采用后进先出行为模式的池。

 

KeyedObjectPool的实现类特点可以参照对应的ObjectPool实现类,基本结构是一致的。

 

 

 

 

通过上面的信息,可以看到主要的池接口就是ObjectPoolKeyedObjectPool,从他们作为入口来进行分析。

ObjectPool

先看一张类图:

 

从图中可以看出来:

 

GenericObjectPool是继承了BaseObjectPoolBaseObjectPool实现了ObjectPool接口,因此GenericObjectPool是间接实现了ObjectPool接口。

 

其中ObjectPoolBaseObjectPool中的setFactory方法在最新的版本2.0中(分析时查看的是svn中的源码)已经被移除,因为基本上在new一个工厂实例的时候就需要放入这个factory,所以这个方法被移除是正常的。

 

并且发现2.0最大的特点就是支持泛型了,也就是说需要JDK1.5以上的支持。

 

org.apache.commons.pool.ObjectPool

 

接口中提供了如下方法:

 

Object borrowObject() // 从池中获得一个对象
void returnObject(Object obj) // 返回一个对象给池
void invalidateObject(Object obj) // 使对象实效,不再受池管辖(必须是已经从池中获得的对象)
void addObject() // 生成一个对象(通过工程或其他实现方式),并将其放入空闲队列中
int getNumIdle() // 获得空闲对象的数量
int getNumActive() // 获得活动对象的数量
void clear() // 清空池中空闲对象,释放相关资源
void close() // 关闭池,释放所有与它相关资源
void setFactory(PoolableObjectFactory factory) // 设置池对象工厂
 

 

org.apache.commons.pool.BaseObjectPool

 

这个类没啥可说的,大部分方法都是简单实现或直接抛异常,稍微看下源码就会明白。

 

org.apache.commons.pool.impl.StackObjectPool

 

构造函数中,就是将池容器一个stack对象初始化,并设定最小容量(默认为4)。

 

borrowObject方法

    public synchronized Object borrowObject() throws Exception {
        assertOpen();
        Object obj = null;
        boolean newlyCreated = false;
        while (null == obj) {
           //  池是否为空
            if (!_pool.empty()) {
              // 栈里有空闲对象,弹出栈中最近放进去的那个对象,即栈顶对象
                obj = _pool.pop();
            } else {
              // 池是空的,这里会先判断工厂是否为空
                if(null == _factory) {
                    throw new NoSuchElementException();
                } else {
                  // 调用工厂的生产新对象
                    obj = _factory.makeObject();
                    newlyCreated = true;
                  if (obj == null) {
                    throw new NoSuchElementException("PoolableObjectFactory.makeObject() returned null.");
                  }
                }
            }
            // 如果生产了对象,需要先激活对象(使其成为初始状态),再进行验证,验证不通过的话,需要销毁这个对象
            if (null != _factory && null != obj) {
                try {
                    _factory.activateObject(obj);
                    if (!_factory.validateObject(obj)) {
                        throw new Exception("ValidateObject failed");
                    }
                } catch (Throwable t) {
                    PoolUtils.checkRethrow(t);
                    try {
                        _factory.destroyObject(obj);
                    } catch (Throwable t2) {
                        PoolUtils.checkRethrow(t2);
                        // swallowed
                    } finally {
                        obj = null;
                    }
                    if (newlyCreated) {
                        throw new NoSuchElementException(
                            "Could not create a validated object, cause: " +
                            t.getMessage());
                    }
                }
            }
        }
       // 活动对象数加一,返回对象
        _numActive++;
        return obj;
    }
 

 

 

过程非常简单,先判断池是否已经关闭,然后进去借对象的循环,直到接到对象或抛出异常为止。

 

returnObject方法

 
public synchronized void returnObject(Object obj) throws Exception {
    // 池是否关闭了
        boolean success = !isClosed();
        if(null != _factory) {
           // 验证对象
            if(!_factory.validateObject(obj)) {
                success = false;
            } else {
                try {
                 // 验证通过的情况下,需要将这个对象设置成空闲对象的状态
                    _factory.passivateObject(obj);
                } catch(Exception e) {
                    success = false;
                }
            }
        }
       // 根据池是否关闭与验证结果,判断这个对象是否应该销毁
        boolean shouldDestroy = !success;
       // 活动对象数减一
        _numActive--;
        if (success) {
           // 如果池中空闲对象已经达到了设定的最大值,那么将池中第一个对象弹出并销毁,将刚刚传入的那个对象压入栈中
            Object toBeDestroyed = null;
            if(_pool.size() >= _maxSleeping) {
                shouldDestroy = true;
                toBeDestroyed = _pool.remove(0); // remove the stalest object
            }
            _pool.push(obj);
            obj = toBeDestroyed; // swap returned obj with the stalest one so it can be destroyed
        }
        notifyAll(); // _numActive has changed
 
       // 进行对象销毁
        if(shouldDestroy) { // by constructor, shouldDestroy is false when _factory is null
            try {
                _factory.destroyObject(obj);
            } catch(Exception e) {
                // ignored
            }
        }
    }
 
 

 

过程依然简单,就是先验证传入的对象,验证通过,就放入池中。

 

addObject方法与returnObject及其类似,就是传入的那个对象,变成了通过工厂方法makeObject来生成,其他过程完全一致,这里不再分析。

其他几个方法invalidateObjectclear等十分简单,也不分析了。

 

总结一下,通过对StackObjectPool的分析,就可以使大家对池的管理有一个总体的认识。

 

池对于对象的管理,主要就是分为借出,归还,验证,销毁,激活,钝化这几个功能。

 

而其中的一些细节就是如何组织这几个功能,比如在借出与归还前都要进行对象的验证,放入池前需要钝化对象等。

 

org.apache.commons.pool.impl.SoftReferenceObjectPool

 

看过StackObjectPool再看SoftReferenceObjectPool,你会发现如此相似,甚至逻辑都一致。

 

最大区别就是放入池中对象是下面这样的:

 

new SoftReference(obj, refQueue)


 

对象被软引用包装了起来,所以使得在内存不足的时候,可以让垃圾回收器强制销毁掉这部分对象,来释放内存。

 

有个比较奇怪的地方就是,在分析源码的过程中,竟然没有找到这个池对象的工厂类...

亲爱的SoftReferenceObjectPoolFactory你到底是藏到哪里去了

 

org.apache.commons.pool.impl.GenericObjectPool

 

这个类比较复杂,前三百多行都是定义的一些常量,基本上就是pool的默认配置信息了。

 

紧接着是构造函数,你会看到n多构造函数,主要是参数不同。

 

第一个参数必然是PoolableObjectFactory(几乎所有的Pool对象实现类都要依赖这个工厂类,后面会详细讲解), 后面的参数是相关设置参数,你可以单独设置,或者使用一个内部类PoolableObjectFactory.Config,里面全是都是public的属性,可以直接修改。

 

关于PoolableObjectFactory.Config的属性,采用默认配置是完全可以正常工作的,当然如果你需要更加细致的控制,你就必须了解这些属性

 

public static class Config {
		// 允许最大空闲对象数
        public int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
 		// 允许最小空闲对象数
        public int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE;
        // 允许最大活动对象数
        public int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
        // 允许最大等待时间毫秒数
        public long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;
        // 当池中对象用完时,请求新的对象所要执行的动作
        public byte whenExhaustedAction = GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION;
        // 是否在从池中取出对象前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
        public boolean testOnBorrow = GenericObjectPool.DEFAULT_TEST_ON_BORROW;
        // 是否在向池中归还对象前进行检验,如果检验失败
        public boolean testOnReturn = GenericObjectPool.DEFAULT_TEST_ON_RETURN;
        // 连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
        public boolean testWhileIdle = GenericObjectPool.DEFAULT_TEST_WHILE_IDLE;
        // 在空闲连接回收器线程运行期间休眠的时间毫秒数. 如果设置为非正数,则不运行空闲连接回收器线程
        public long timeBetweenEvictionRunsMillis = GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
        // 设定在进行后台对象清理时,每次检查对象数
        public int numTestsPerEvictionRun =  GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
        // 被空闲对象回收器回收前在池中保持空闲状态的最小时间毫秒数
        public long minEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
        // 被空闲对象回收器回收前在池中保持空闲状态的最小时间毫秒数
        public long softMinEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
        // 是否采用后进先出策略
        public boolean lifo = GenericObjectPool.DEFAULT_LIFO;
    }
 

 

从六百行到一千行这部分,是上面修改参数的方法,其中主要涉及一个allocate方法,会从新分配空闲对象。

 

下面分析下这个方法:

 

    private synchronized void allocate() {
        if (isClosed()) return;
 
        // First use any objects in the pool to clear the queue
        for (;;) {
            if (!_pool.isEmpty() && !_allocationQueue.isEmpty()) {
                Latch latch = (Latch) _allocationQueue.removeFirst();
                latch.setPair((ObjectTimestampPair) _pool.removeFirst());
                _numInternalProcessing++;
                synchronized (latch) {
                    latch.notify();
                }
            } else {
                break;
            }
        }
 
        // Second utilise any spare capacity to create new objects
        for(;;) {
            if((!_allocationQueue.isEmpty()) && (_maxActive < 0 || (_numActive + _numInternalProcessing) < _maxActive)) {
                Latch latch = (Latch) _allocationQueue.removeFirst();
                latch.setMayCreate(true);
                _numInternalProcessing++;
                synchronized (latch) {
                    latch.notify();
                }
            } else {
                break;
            }
        }
    }
 
 

 

首先判断该池是否已经关闭

 

然后

 

再然后

 

额,暂时没有完全看懂,实在不好乱讲,以后再详细补完这部分。

 

borrowObjcetreturnObject 也稍后补完。

 

 

下面看一下一个比较重要的类,基本上你要使用Pool框架,都要与它打交道

org.apache.commons.pool.PoolableObjectFactory

 

这是一个接口类,定义了生成对象,销毁对象,验证对象,激活对象,钝化对象的方法。

 

Object makeObject()
void destroyObject(Object obj)
boolean validateObject
void activateObject(Object obj)
void passivateObject(Object obj)
 

 

当时要使用Commons-Pools时,你就需要新建一个类,实现这个接口,然后实例化你需要的工厂类(StackObjectPoolFactoryGenericObjectPoolFactory,根据你的需要来选择吧),然后用工厂生成你的池对象(Pool)。

 

 

 

KeyedObjectPool

同样也来看一张类图:

 

可以看出来KeyedObjectPool类结构与ObjectPool很类似,就是很多方法中多一个key参数而已,如果查看源码也会发现,整个代码逻辑也基本类似,那到底KeyedObjectPool是为了解决什么问题而提出来的呢?

 

就个人理解,在使用普通的池的时候,你只能通过这个池获得一种对象,如果你想获得不同的对象要怎么办呢?那就分组吧,将不同类型的对象放在不同的池中,但这样你是不是就要维护多个池呢。

 

针对这个问题,map的数据结构可以给你提供一个好思路,新建一个mapkey为组名称,value为对应的池。就是相当于提供一个更大的容器,来存放多个池,你只需要管理这个大容器就好了。

 

举个现实中的例子,比如你去小卖部买饮料(调用borrowObject)。

 

这里的小卖部就是一个KeyedObjectPool,可乐就相当于放在可乐池(可乐Pool)中对象,雪碧是放在雪碧池(雪碧Pool)中的对象,当然一般他们会放在一个冰柜中(冰柜Map)。

 

当你在小卖部(KeyedObjectPool)买饮料时,小卖部老板会根据你的要求(你提供的key,假如你要可乐),到冰柜中寻找可乐池(调用冰柜Mapget方法),然后从返回的池(可乐池)中拿出你要的可乐(如果是stack,会调用pop方法),最后交给你。

 

基本思路就是这样,当然代码中会有一些差别。

 

所以这部分代码,你只要看懂了ObjectPool的,这里就不会有任何难度了,只是多了一个Map容易而已。

 

 

#######################邪恶的分割线#######################

 

ApacheSVN中拉下的代码来看,trunk中已经是2.0版的代码了,除了加入泛型以外,就是修正了一些方法与参数(1.5.6中已经有很多注释提示了会在2.0中修正),但基本的思路没有变化,所以可以继续研究学习1.5.X的代码。

 

 

最后总结下,Commons-Pool到底是为我们做了哪些?

 

首先它定义了池管理方面的API,并考虑了多线程,多种数据结构,内容不足等情况。

 

其次它为我们提供一个同时管理多个池的解决方案。

 

留给使用者,主要就是PoolableObjectFactoryKeyedPoolableObjectFactory

 

只要使用者将对象的生成,销毁,验证,激活与钝化做好,做到完善,没有歧义(比如什么样的状态算是激活,什么是钝化后的状态),那剩下的就可以放心的交给Commons-Pool来管理你的池了。

最好的一个例子也算是官方例子吧,就是Commons-DBCP(数据库连接池),等下次来分析下DBCP是怎么使用Pool来管理数据库连接的。

 

最后贴一段提示,告诉你到底什么时候你才会需要一个池:

 

 

什么时候不要池化

 

采用对象池化的本意,是要通过减少对象生成的次数,减少花在对象初始化上面的开销,从而提高整体的性能。然而池化处理本身也要付出代价,因此,并非任何情况下都适合采用对象池化。

Dr. Cliff ClickJavaOne 2003上发表的《Performance Myths Exposed》中,给出了一组其它条件都相同时,使用与不使用对象池化技术的实际性能的比较结果。他的实测结果表明:

 

对于类似Point这样的轻量级对象,进行池化处理后,性能反而下降,因此不宜池化;

 

对于类似Hashtable这样的中量级对象,进行池化处理后,性能基本不变,一般不必池化(池化会使代码变复杂,增大维护的难度);

 

对于类似JPanel这样的重量级对象,进行池化处理后,性能有所上升,可以考虑池化。

 

根据使用方法的不同,实际的情况可能与这一测量结果略有出入。在配置较高的机器和技术较强的虚拟机上,不宜池化的对象的范围可能会更大。不过,对于像网络和数据库连接这类重量级的对象来说,目前还是有池化的必要。

基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合进行对象池化。如果进行池化所能带来的性能提高并不重要的话,还是不采用对象池化技术,以保持代码的简明,而使用更好的硬件和更棒的虚拟机来提高性能为佳。

 

 

5
1
分享到:
评论
1 楼 lipanpally 2011-08-17  
谢谢分享。

相关推荐

    java连接池有关jar:commons-pool-1.2.jar+commons-pool-1.3.jar+commons-pool.jar

    commons-pool-1.3.jar+commons-pool.jar;java连接池jar包java连接池;java连接池jar;commons-pool-1.2.jar;commons-pool-1.3.jar+commons-pool.jar;java连接池jar包java连接池;java连接池jar;commons-pool-1.2.jar;...

    commons-pool2-2.0-API文档-中文版.zip

    Maven坐标:org.apache.commons:commons-pool2:2.0; 标签:apache、commons、pool2、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,...

    commons-pool2-2.10.0-API文档-中文版.zip

    Maven坐标:org.apache.commons:commons-pool2:2.10.0; 标签:apache、commons、pool2、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译...

    commons-pool2-2.3-API文档-中文版.zip

    Maven坐标:org.apache.commons:commons-pool2:2.3; 标签:apache、pool2、commons、jar包、java、API文档、中文版; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化...

    commons-pool2-2.4.3-API文档-中文版.zip

    Maven坐标:org.apache.commons:commons-pool2:2.4.3; 标签:apache、pool2、commons、jar包、java、中文文档; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,...

    commons-pool2-2.10.0-API文档-中英对照版.zip

    Maven坐标:org.apache.commons:commons-pool2:2.10.0; 标签:apache、pool2、commons、jar包、java、中英对照文档; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化...

    commons-pool2-2.5.0-API文档-中英对照版.zip

    对应Maven信息:groupId:org.apache.commons,artifactId:commons-pool2,version:2.5.0 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构...

    开发工具 commons-pool2-2.4.2

    开发工具 commons-pool2-2.4.2开发工具 commons-pool2-2.4.2开发工具 commons-pool2-2.4.2开发工具 commons-pool2-2.4.2开发工具 commons-pool2-2.4.2开发工具 commons-pool2-2.4.2开发工具 commons-pool2-2.4.2开发...

    commons-pool2-2.11.1-bin.zip

    DBCP(DataBase Connection Pool)是 apache common上的一个 java 连接池项目,也是 tomcat 使用的连接池组件,依赖 于Jakarta commons-pool 对象池机制,DBCP可以直接的在应用程序中使用。 使用DBCP会用到commons-...

    commons-pool.jar

    commons-pool-1.1.jar, commons-pool-1.2.jar, commons-pool-1.3-src.jar, commons-pool-1.3.jar, commons-pool-1.4.jar, commons-pool-1.5.1.jar, commons-pool-1.5.2-sources.jar, commons-pool-1.5.2.jar, ...

    commo-pool, commons-pool commons-pool commons-pool

    commo-pool, commons-pool commons-pool commons-pool

    commons-pool-1.5.6-bin.zip

    Apache commons-pool本质上是"对象池",即通过一定的规则来维护对象集合的容器;commos-pool在很多场景中,用来实现"连接池"/"任务worker池"等,大家常用的dbcp数据库连接池,也是基于commons-pool实现.

    commons-dbcp-1.4.jar和commons-pool-1.5.6.jar

    commons-dbcp-1.4.jar和commons-pool-1.5.6.jar

    commons-pool-1.6-API文档-中文版.zip

    赠送jar包:commons-pool-1.6.jar; 赠送原API文档:commons-pool-1.6-javadoc.jar; 赠送源代码:commons-pool-1.6-sources.jar; 赠送Maven依赖信息文件:commons-pool-1.6.pom; 包含翻译后的API文档:commons-...

    commons-dbcp-1.2.2.jar和commons-pool-1.3.jar包

    commons-dbcp-1.2.2.jar和commons-pool-1.3.jar包,数据源包

    commons-pool2-2.9.0-API文档-中文版.zip

    Maven坐标:org.apache.commons:commons-pool2:2.9.0; 标签:apache、commons、pool2、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,...

    apache commons 常用jar包 commons-validator commons-transaction commons-lang等

    jar包大小:60KB commons-pool-1.3.jar jar包大小:176KB commons-net-1.4.1.jar jar包大小:109KB commons-modeler-2.0.1.jar jar包大小:170KB commons-math-1.1.jar jar包大小:43KB commons-logging-api-1.1.jar jar...

    commons-dbcp-1.4和commons-pool-1.6驱动包下载(亲测可用)

    commons-dbcp-1.4和commons-pool-1.6驱动包下载(亲测可用)

    commons-pool-1.6.jar

    commons-pool-1.6.jar DBCP数据库连接池必不可少的jar包

    commons-pool2-2.4.2-API文档-中英对照版.zip

    对应Maven信息:groupId:org.apache.commons,artifactId:commons-pool2,version:2.4.2 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构...

Global site tag (gtag.js) - Google Analytics