`
pangwu86
  • 浏览: 117762 次
  • 性别: 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  
谢谢分享。

相关推荐

    Apache commons-pool2-2.4.2源码学习笔记

    通过分析这些图像和源码,我们可以了解到Apache Commons Pool2的内部工作机制,如对象的生命周期管理、池的配置和性能优化等方面的知识。此外,源码阅读也能帮助我们理解设计模式的应用,如工厂模式、池模式等,并能...

    commons-pool-1.2-src.zip

    Apache Commons Pool 是一个开源项目,提供了对象池化的实现,它是一个通用的对象池库,用于创建各种资源对象的池,如数据库连接池。标题中的"commons-pool-1.2-src.zip"表明这是一个包含Apache Commons Pool 1.2...

    commons-pool-1.5.4-src.tar.gz

    《Apache Commons Pool 1.5.4 源码解析》 Apache Commons Pool 是一个开源的、基于Java的对象池库,它为开发者提供了一种有效地管理资源的方式,特别是那些昂贵创建但轻量级使用的对象。这个库的核心是对象池模式的...

    commons-dbcp源码

    通过阅读和分析"commons-dbcp-1.2.2-src"源码,可以深入了解数据库连接池的实现细节,包括连接的创建、复用、销毁过程,以及异常处理和性能调优策略。这对于开发和维护自己的数据库连接池或者优化现有应用的数据库...

    commons-pool1.5.2 jar包+源码

    源码分析有助于定制自己的池实现或者优化使用Apache Commons Pool的方式。 总结来说,Apache Commons Pool 1.5.2 是一个强大的Java对象池库,可以帮助开发者提高应用程序的效率。通过合理的配置和使用,它可以有效...

    commons-pool2-2.4.2-src

    源码分析有助于开发者掌握如何设计和使用高效的对象池,并且可以定制自己的对象池策略。 1. **核心接口和类**: - `ObjectPool&lt;T&gt;`:这是Pool的核心接口,定义了对象池的基本操作,如`borrowObject()`, `return...

    commoms-pool-1.5.4-sources.jar.zip

    四、源码分析 1. **borrowObject()**:这是获取对象的主要方法,它会检查池中是否有可用对象,如果没有,则尝试创建新的对象。如果达到最大限制,将抛出异常。 2. **returnObject()**:归还对象到池中,对象会经过...

    commons-dbcp-1.2.2源码

    源码分析对于理解其工作原理和优化应用至关重要。在"commons-dbcp-1.2.2源码"中,我们可以深入探讨以下几个关键知识点: 1. **数据库连接池的概念**:数据库连接池是一种在初始化阶段创建一定数量的数据库连接,并...

    commons-pool2-sr:Apache Commons Pool原始代码剖析笔记

    在Apache Commons Pool2的源代码分析中,我们可以深入理解以下几个核心知识点: 1. **对象池的基本原理**:对象池的主要思想是预先创建一组对象并存储在一个池中,当需要对象时,不是直接创建新的,而是从池中获取...

    commons-dbcp-1.3

    6. **源码分析**: `commons-dbcp-1.3-src.zip`包含了DBCP 1.3的源代码,开发者可以深入理解其内部机制,进行定制化开发或者排查问题。 7. **依赖与集成**: DBCP通常与其他Java数据库访问组件一起使用,如JDBC...

    commons-dbcp-1.4源码

    Apache Commons DBCP(Database Connection Pool)是Apache软件基金会提供的一款开源数据库连接池组件,版本1.4是我们这里关注的重点。数据库连接池在Java应用程序中扮演着至关重要的角色,它提高了应用性能,优化了...

    commons-dbcp2-2.0.1-src

    《Apache Commons DBCP2 2.0.1 源码分析》 Apache Commons DBCP2(DBCP第二版)是Apache软件基金会提供的一款开源数据库连接池组件,主要用于管理数据库连接,提高应用程序的效率和性能。在版本2.0.1中,DBCP2已经...

    commons-dbcp-1.3-src.zip

    Apache Commons DBCP,全称为"Database Connection Pool",是Apache软件基金会开发的一个开源数据库连接池组件。在Java应用程序中,DBCP作为一个高效、稳定、成熟的数据库连接池解决方案,广泛应用于各种项目中,以...

    lib-ssh

    6. `commons-dbcp-1.4.jar` - Apache Commons DBCP是基于Apache Commons Pool和Jakarta Pool实现的数据库连接池,用于管理数据库连接资源。 7. `hibernate-jpa-2.0-api-1.0.0.Final.jar` - Hibernate JPA API,提供...

    heritrix的学习-源码分析 1-10

    ### Heritrix源码分析知识点概述 #### 一、Heritrix简介 Heritrix是一款开源的网络爬虫工具,由Internet Archive开发并维护。它主要用于网页归档和大规模网络爬取任务。Heritrix的设计理念是高度可配置性和扩展性,...

    dbutils的jar包和源码

    源码分析** `commons-dbutils-1.4-bin.zip` 包含了 dbutils 的已编译 JAR 文件,可以用于项目依赖。`commons-dbutils-1.4-src.zip` 则提供了源代码,开发者可以深入研究其内部实现,了解每个类和方法的工作原理,...

    开源dbcp,获取datasource

    标题中的“开源dbcp,获取datasource”指的是Apache Commons DBCP(Database Connection Pool),这是一个在Java环境下广泛使用的开源数据库连接池组件。Apache Commons DBCP是Apache软件基金会的一个项目,它提供了...

    tomcat 异常

    5. **线程池和连接管理**:描述中提到的`excalibur-pool-1.2.jar`是Apache Commons Pool库,它用于对象池管理,可能涉及到线程池配置不当导致的问题。 6. **网络通信**:`commons-net-3.0.1.jar`提供了TCP/IP协议栈...

    数据库连接池

    - **DBCP**:Apache基础软件基金会的项目,基于Jakarta-Commons-Pool和Jakarta-Commons-Dbcp。 - **HikariCP**:高性能的连接池,设计目标是提供最小的延迟和最高的并发性能。 - **Druid**:阿里巴巴开源的数据库...

    数据库连接池BoneCP源码分析报告

    6. 分布式环境支持:虽然BoneCP本身不直接支持分布式环境,但可以通过配置中间件如Apache Commons Pool或HikariCP来实现跨节点的连接池共享。 源码分析时,我们可以关注以下关键类和方法: - `...

Global site tag (gtag.js) - Google Analytics