Java 对象池技术


Apache 的commons-pool 提供了编写对象池的 API,将用完的对象返回对象池中 以便于下次利用,从而减少了对象创建时间。这对于创建对象相对耗时的应用来 说,能够提高应用的性能。 commons-dbcp 数据库连接池正是使用 commons-pool 来创建和数据库之间的连接 对象,在对象池中保存这些对象,从而减少了频繁建立连接对象所造成的性能损 耗。本文讲述了 commons-dbcp 是怎样利用 commons-pool 来建立对象池的。 例如在 tomcat 中,利用 JNDI 来查找到资源 javax.sql.DataSource 的实现,如 果使用 dbcp 连接池,则该实现为 org.apache.commons.dbcp.BasicDataSource。 我们可以从这个称为“数据源”的类中调用 getConnection 方法来获得与数据 库的连接。但内部是如何实现的呢?对象池在其中占据什么位置呢?这一切对于 外部使用者来说是透明的。 BasicDataSource 的getConnection 首先建立了 PoolingDataSource 对象来 getConnection。这个 PoolingDataSource 对象中引用了 ObjectPool,在 getConnection()时,是从 ObjectPool 中借用了一个对象,既调用 ObjectPool.borrowObject()方法。而对于熟悉 commons-pool 的程序员来说, ObjectPool 肯定有与之对应的 Factory 创建对象,并放到池中。因此 dbcp通过 实现了接口 org.apache.commons.pool.PoolableObjectFactory 的类 org.apache.commons.dbcp.PoolableConnectionFactory 的makeObject 方法来 创建连接 Connection 对象。然而 PoolableConnectionFactory 持有对 ConnectionFactory 的引用,ConnectionFactory 可以有 3种策略来创建 Connection 对象。其中 DriverConnectionFactory 调用了数据库厂商提供的 Driver 来获得 Connection。 最近在做一个内部测试工具类的优化工作中接触到了连接池, 对象池技术, 将 原有的未使用连接池的数据库访问操作改成连接池方式.性能有了非常大的提升, 事实证明, 经过两次改造, 原来一个比较大的测试类需要 500 多秒, 第一次优 化后只需要300 多秒, 第二次改用连接池之后同一个测试类只需要80 多秒.下面 是改造过程中的一些总结. 对象池就是以"空间换时间"的一种常用缓存机制, 这里的"时间"特指创建时间, 因此这也给出了对象池的适用范围:如果一种对象的创建过程非常耗时的话, 那 么请使用对象池. 内部原理简单的说, 就是将创建的对象放到一个容器中, 用 完之后不是销毁而是再放回该容器, 让其他的对象调用, 对象池中还涉及到一 些高级的技术, 比如过期销毁, 被破坏时销毁, 对象数超过池大小销毁, 对象 池中没有可用空闲对象时等待等等. apache 的common-pool 工具库是对池化技术原理的一种具体实现. 在阐述原来 之前, 这里先理解几个概念: 对象池(ObjectPool 接口): 可以把它认为是一种容器, 它是用来装池对象的, 并且包含了用来创建池对象的工厂对象 池对象:就是要放到池容器中的对象, 理论上可以是任何对象. 对象池工厂(ObjectPoolFactory 接口):用来创建对象池的工厂, 这个没什么好 说的. 池对象工厂(PoolableObjectFactory 接口):用来创建池对象, 将不用的池对象 进行钝化(passivateObject), 对要使用的池对象进行激活(activeObject), 对 池对象进行验证(validateObject), 对有问题的池对象进行销毁 (destroyObject)等工作 对象池中封装了创建, 获取, 归还, 销毁池对象的职责, 当然这些工作都是通 过池对象工厂来实施的, 容器内部还有一个或多个用来盛池对象的容器.对象池 会对容器大小, 存放时间, 访问等待时间, 空闲时间等等进行一些控制, 因为 可以根据需要来调整这些设置. 当需要拿一个池对象的时候, 就从容器中取出一个, 如果容器中没有的话, 而 且又没有达到容器的最大限制, 那么就调用池对象工厂, 新建一个池对象, 并 调用工厂的激活方法, 对创建的对象进行激活, 验证等一系列操作. 如果已经 达到池容器的最大值, 而对象池中又经没有空闲的对象, 那么将会继续等待, 直到有新的空闲的对象被丢进来, 当然这个等待也是有限度的, 如果超出了这 个限度, 对象池就会抛出异常. "出来混, 总是要还的", 池对象也是如此, 当将用完的池对象归还到对象池中 的时候, 对象池会调用池对象工厂对该池对象进行验证, 如果验证不通过则被 认为是有问题的对象, 将会被销毁, 同样如果容器已经满了, 这个归还池对象 将变的"无家可归", 也会被销毁, 如果不属于上面两种情况, 对象池就会调用 工厂对象将其钝化并放入容器中. 在整个过程中, 激活, 检查, 钝化处理都不 是必须的, 因此我们在实现 PoolableObjectFactory 接口的时候, 一般不作处 理, 给空实现即可, 所以诞生了 BasePoolableObjectFactory. 当然你也可以将要已有的对象创建好, 然后通过 addObject 放到对象池中去, 以备后用. 为了确保对对象池的访问都是线程安全的, 所有对容器的操作都必须放在 synchronized 在apache 的common-pool 工具库中有 5种对象池:GenericObjectPool 和 GenericKeyedObjectPool, SoftReferenceObjectPool, StackObjectPool, StackKeyedObjectPool. 五种对象池可分为两类, 一类是无 key 的: 另一类是有 key 的: 前面两种用 CursorableLinkedList 来做容器, SoftReferenceObjectPool 用 ArrayList 做容器, 一次性创建所有池化对象, 并对容器中的对象进行了软引 用(SoftReference)处理, 从而保证在内存充足的时候池对象不会轻易被 jvm 垃 圾回收, 从而具有很强的缓存能力. 最后两种用Stack 做容器. 不带key 的对象 池是对前面池技术原理的一种简单实现, 带key 的相对复杂一些, 它会将池对 象按照 key 来进行分类, 具有相同的 key 被划分到一组类别中, 因此有多少个 key, 就会有多少个容器. 之所以需要带 key 的这种对象池, 是因为普通的对象 池通过 makeObject()方法创建的对象基本上都是一模一样的, 因为没法传递参 数来对池对象进行定制. 因此四种池对象的区别主要体现在内部的容器的区别, Stack 遵循"后进先出"的原则并能保证线程安全, CursorableLinkedList 是一个 内部用游标(cursor)来定位当前元素的双向链表, 是非线程安全的, 但是能满 足对容器的并发修改.ArrayList 是非线程安全的, 便利方便的容器. 使用对象池的一般步骤:创建一个池对象工厂, 将该工厂注入到对象池中, 当要 取池对象, 调用 borrowObject, 当要归还池对象时, 调用 returnObject, 销毁 池对象调用 clear(), 如果要连池对象工厂也一起销毁, 则调用 close(). 下面是一些时序图: borrowObject: returnObject: invalidateObject: apache 的连接池工具库 common-dbcp 是common-pool 在数据库访问方面的一个 具体应用.当对 common-pool 熟悉之后, 对common-dbcp 就很好理解了. 它通过 对已有的 Connection, Statment 对象包装成池对象 PoolableConnection, PoolablePreparedStatement. 然后在这些池化的对象中, 持有一个对对象池的 引用, 在关闭的时候, 不进行真正的关闭处理, 而是通过调用: Java 代码 1. _pool.returnObject(this); 或: Java 代码 1. _pool.returnObject(_key,this); 这样一句, 将连接对象放回连接池中. 而对应的对象池前者采用的是 ObjectPool, 后者是 KeyedObjectPool, 因为一 个数据库只对应一个连接, 而执行操作的Statement 却根据Sql 的不同会分很多 种. 因此需要根据 sql 语句的不同多次进行缓存 在对连接池的管理上, common-dbcp 主要采用两种对象: 一个是 PoolingDriver, 另一个是 PoolingDataSource, 二者的区别是 PoolingDriver 是一个更底层的操作类, 它持有一个连接池映射列表, 一般针 对在一个 jvm 中要连接多个数据库, 而后者相对简单一些. 内部只能持有一个 连接池, 即一个数据源对应一个连接池. 下面是 common-dbcp 的结构关系: 下面是参考了 common-dbcp 的例子之后写的一个从连接池中获取连接的工具类 Java 代码 1. /** 2. * 创建连接 3. * 4. *@since 2009-1-22 下午 02:58:35 5. */ 6. public class ConnectionUtils { 7. // 一些 common-dbcp 内部定义的 protocol 8. private static final String POOL_DRIVER_KEY = "jdbc:apache: commons:dbcp:"; 9. private static final String POLLING_DRIVER = "org.apache.co mmons.dbcp.PoolingDriver"; 10. 11. /** 12. * 取得池化驱动器 13. * 14. *@return 15. *@throws ClassNotFoundException 16. *@throws SQLException 17. */ 18. private static PoolingDriver getPoolDriver() throws ClassNo tFoundException, 19. SQLException { 20. Class.forName(POLLING_DRIVER); 21. return (PoolingDriver) DriverManager.getDriver(POOL_DRI VER_KEY); 22. } 23. 24. /** 25. * 销毁所有连接 26. * 27. *@throws Exception 28. */ 29. public static void destory() throws Exception { 30. PoolingDriver driver = getPoolDriver(); 31. String[] names = driver.getPoolNames(); 32. for (String name : names) { 33. driver.getConnectionPool(name).close(); 34. } 35. } 36. 37. /** 38. * 从连接池中获取数据库连接 39. */ 40. public static Connection getConnection(TableMetaData table) 41. throws Exception { 42. String key = table.getConnectionKey(); 43. 44. PoolingDriver driver = getPoolDriver(); 45. 46. ObjectPool pool = null; 47. // 这里找不到连接池会抛异常, 需要 catch 一下 48. try { 49. pool = driver.getConnectionPool(key); 50. } catch (Exception e) { 51. } 52. 53. if (pool == null) { 54. // 根据数据库类型构建连接工厂 55. ConnectionFactory connectionFactory = null; 56. if (table.getDbAddr() != null 57. && TableMetaData.DB_TYPE_MYSQL == table.get DbType()) { 58. Class.forName(TableMetaData.MYSQL_DRIVER); 59. connectionFactory = new DriverManagerConnection Factory(table 60. .getDBUrl(), null); 61. } else { 62. Class.forName(TableMetaData.ORACLE_DRIVER); 63. connectionFactory = new DriverManagerConnection Factory(table 64. .getDBUrl(), table.getDbuser(), table.g etDbpass()); 65. } 66. 67. // 构造连接池 68. ObjectPool connectionPool = new GenericObjectPool(n ull); 69. new PoolableConnectionFactory(connectionFactory, co nnectionPool, 70. null, null, false, true); 71. 72. // 将连接池注册到 driver 中 73. driver.registerPool(key, connectionPool); 74. } 75. 76. // 从连接池中拿一个连接 77. return DriverManager.getConnection(POOL_DRIVER_KEY + ke y); 78. } 79. 80.} Java 对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可 用如下的表达式表示:T = T1 + T2 +T3。其中 T1表示对象的创建时间,T2表示对象的使用时间,而 T3则表示 其清除时间。由此,我们可以看出,只有 T2是真正有效的时间,而 T1、T3则 是对象本身的开销。下面再看看 T1、T3在对象的整个生命周期中所占的比例。 我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自 动调用。另外,默认情况下,调用类的构造函数 时,Java 会把变量初始化成确定的值:所有的对象被设置成 null, 整数变量(byte、short、int、long)设置成 0,float和 double变量设置成 0.0,逻辑值设置成 false。所以用 new 关键字来新建一个对象的时间开销是很大的,如表 1所示。 表1 一些操作所耗费时间的对照表 运算操作 示例 标准化时间 本地赋值 i = n 1.0 实例赋值 this.i = n 1.2 方法调用 Funct() 5.9 新建对象 New Object() 980 新建数组 New int[10] 3100 从表 1 可以看出,新建一个对象需要 980个单位的时间,是本地赋值时间的 980 倍,是方法调用时间的 166 倍,而若新建一个数组所花费的时间就更多了。 再看清除对象的过程。我们知道,Java语言的一个优势,就是 Java 程序员勿需再像 C/C++程序员那样,显 式地释放对象,而由称为垃圾收集器 (Garbage Collector)的自动内存管理系统,定时或在内存凸现出不足时, 自动回收垃圾对象所占的内存。凡事有利总也有弊,这虽然为 Java程序设计者提供了 极大的方便,但同时它也 带来了较大的性能开销。这种开销包括两方面,首先是对象管理开销,GC为了能够正确释放对象,它必须监控每 一个对象的运行状态,包 括对象的申请、引用、被引用、赋值等。其次,在GC开始回收“垃圾”对象时,系统 会暂停应用程序的执行,而独自占用 CPU。 因此,如果要改善应用程序的性能,一方面应尽量减少创建新对象的次数;同时,还应尽量减少 T1、T3的时 间,而这些均可以通过对象池技术来实现。 对象池技术的基本原理 对象池技术基本原理的核心有两点:缓存和共享,即对于那些被频繁使用的对象,在使用完后,不立即将它 们释放,而是将它们缓存起来,以供后续的应用程序重 复使用,从而减少创建对象和释放对象的次数,进而改 善应用程序的性能。事实上,由于对象池技术将对象限制在一定的数量,也有效地减少了应用程序内存上的开 销。 实现一个对象池,一般会涉及到如下的类: 1)对象池工厂(ObjectPoolFactory)类 该类主要用于管理相同类型和设置的对象池(ObjectPool),它一般包含如下两个方法: ·createPool:用于创建特定类型和设置的对象池; ·destroyPool:用于释放指定的对象池; 同时为保证 ObjectPoolFactory的单一实例,可以采用 Singleton设计模式,见下述 getInstance方法的实 现: public static ObjectPoolFactory getInstance() { if (poolFactory == null){ poolFactory = new ObjectPoolFactory(); } return poolFactory; } 2)参数对象(ParameterObject)类 该类主要用于封装所创建对象池的一些属性参数,如池中可存放对象的数目的最大值(maxCount)、最小值 (minCount)等。 3)对象池(ObjectPool)类 用于管理要被池化对象的借出和归还,并通知 PoolableObjectFactory完成相应的工作。它一般包含如下两 个方法: ·getObject:用于从池中借出对象; ·returnObject:将池化对象返回到池中,并通知所有处于等待状态的线程; 4)池化对象工厂(PoolableObjectFactory)类 该类主要负责管理池化对象的生命周期,就简单来说,一般包括对象的创建及销毁。该类同 ObjectPoolFactory一样,也可将其实现为单实例。 通用对象池的实现 对象池的构造和管理可以按照多种方式实现。最灵活的方式是将池化对象的 Class 类型在对象池之外指定, 即在 ObjectPoolFactory类创建对象池时,动态指定该对象池所池化对象的 Class 类型,其实现代码如下: ... public ObjectPool createPool(ParameterObject paraObj,Class clsType) { return new ObjectPool(paraObj, clsType); } ... 其中,paraObj参数用于指定对象池的特征属性,clsType 参数则指定了该对象池所存放对象的类型。对象 池(ObjectPool)创建以后,下面就是利用它来管理对象了,具体实现如下: public class ObjectPool { private ParameterObject paraObj;//该对象池的属性参数对象 private Class clsType;//该对象池中所存放对象的类型 private int currentNum = 0; //该对象池当前已创建的对象数目 private Object currentObj;//该对象池当前可以借出的对象 private Vector pool;//用于存放对象的池 public ObjectPool(ParameterObject paraObj, Class clsType) { this.paraObj = paraObj; this.clsType = clsType; pool = new Vector(); } public Object getObject() { if (pool.size() < = paraObj.getMinCount()) { if (currentNum < = paraObj.getMaxCount()) { //如果当前池中无对象可用,而且已创建的对象数目小于所限制的最大值, 就利用 //PoolObjectFactory创建一个新的对象 PoolableObjectFactory objFactory =PoolableObjectFactory.getInstance(); currentObj = objFactory.create Object (clsType); currentNum++; } else { //如果当前池中无对象可用,而且所创建的对象数目已达到所限制的最大 值, //就只能等待其它线程返回对象到池中 synchronized (this){ try { wait(); } catch (InterruptedException e) { System.out.println(e.getMessage()); e.printStackTrace(); } currentObj = pool.firstElement(); } } } else { //如果当前池中有可用的对象,就直接从池中取出对象 currentObj = pool.firstElement(); } return currentObj; } public void returnObject(Object obj) { // 确保对象具有正确的类型 if (obj.isInstance(clsType)) { pool.addElement(obj); synchronized (this){ notifyAll(); } } else { throw new IllegalArgumentException( "该对象池不能存放指定的对象类 型"); } } } 从上述代码可以看出,ObjectPool利用一个 java.util.Vector 作为可扩展的对象池,并通过它的构造函数 来指定池化对象的 Class类型及对象池的一些属性。在有对象返回到对象池时,它将检查对象的类型是否正确。 当对象池里不再有可用对象时,它或者等待已被使用的池化对象返 回池中,或者创建一个新的对象实例。不过, 新对象实例的创建并不在 ObjectPool类中,而是由 PoolableObjectFactory类的 createObject方法来完成的, 具体实现如下: ... public Object createObject(Class clsType) { Object obj = null; try { obj = clsType.newInstance(); } catch (Exception e) { e.printStackTrace(); } return obj; } ... 这样,通用对象池的实现就算完成了,下面再看看客户端(Client)如何来使 用它,假定池化对象的 Class类型 为StringBuffer: ... //创建对象池工厂 ObjectPoolFactory poolFactory = ObjectPoolFactory. getInstance (); //定义所创建对象池的属性 ParameterObject paraObj = new ParameterObject(2,1); //利用对象池工厂,创建一个存放 StringBuffer类型对象的对象池 ObjectPool pool = poolFactory.createPool(paraObj,String Buffer.class); //从池中取出一个 StringBuffer对象 StringBuffer buffer = (StringBuffer)pool.getObject(); //使用从池中取出的 StringBuffer对象 buffer.append("hello"); System.out.println(buffer.toString()); ... 可以看出,通用对象池使用起来还是很方便的,不仅可以方便地避免频繁创建对象的开销,而且通用程度高。 但遗憾的是,由于需要使用大量的类型定型 (cast)操作,再加上一些对 Vector类的同步操作,使得它在某些情 况下对性能的改进非常有限,尤其对那些创建周期比较短的对象。 专用对象池的实现 由于通用对象池的管理开销比较大,某种程度上抵消了重用对象所带来的大部分优势。为解决该问题,可以 采用专用对象池的方法。即对象池所池化对象的 Class类型不是动态指定的,而是预先就已指定。这样,它在实 现上也会较通用对象池简单些,可以不要 ObjectPoolFactory和 PoolableObjectFactory类,而将它们的功能直 接融合到 ObjectPool 类,具体如下(假定被池化对象的 Class类型仍为 StringBuffer,而用省略号表示的地方, 表示代码同通用对象池的实现): public class ObjectPool { private ParameterObject paraObj;//该对象池的属性参数对象 private int currentNum = 0; //该对象池当前已创建的对象数目 private StringBuffer currentObj;//该对象池当前可以借出的对象 private Vector pool;//用于存放对象的池 public ObjectPool(ParameterObject paraObj) { this.paraObj = paraObj; pool = new Vector(); } public StringBuffer getObject() { if (pool.size() < = paraObj.getMinCount()) { if (currentNum < = paraObj.getMaxCount()) { currentObj = new StringBuffer(); currentNum++; } ... } return currentObj; } public void returnObject(Object obj) { // 确保对象具有正确的类型 if (StringBuffer.isInstance(obj)) { ... } } 结束语 恰当地使用对象池技术,能有效地改善应用程序的性能。目前,对象池技术已得到广泛的应用,如对于网络 和数据库连接这类重量级的对象,一般都会采用对象池技术。但在使用对象池技术时也要注意如下问题: ·并非任何情况下都适合采用对象池技术。基本上,只在重复生成某种对象的操作成为影响性能的关键因素 的时候,才适合采用对象池技术。而如果进行池化所能带来的性能提高并不重要的话,还是不采用对象池化技术 为佳,以保持代码的简明。 ·要根据具体情况正确选择对象池的实现方式。如果是创建一个公用的对象池技术实现包,或需要在程序中 动态指定所池化对象的 Class 类型时,才选择通用对象池。而大部分情况下,采用专用对象池就可以了。
还剩14页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

落落的月

贡献于2012-04-14

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf