栏目:缓存设计 作者:admin 日期:2015-03-04 评论:1 点击: 2,085 次
对象池就是以"空间换时间"的一种常用缓存机制,这里的"时间"特指创建时间。如果一种对象的创建过程非常耗时的话,那么请使用对象池。从内部原理简单的说,对象池技术就是将创建的对象放到一个容器中,用完之后不是销毁而是再放回该容器,让其他的对象调用,对象池中还涉及到一些高级的技术,比如过期销毁,被破坏时销毁,对象数超过池大小销毁,对象池中没有可用空闲对象时等待等等。
对象池技术,在数据库连接过程中使用的比较多,将原有的未使用连接池的数据库访问操作改成连接池方式,性能有了非常大的提升。最近在做一个内部测试工具类的优化工作中接触到了连接池,事实证明,经过两次改造,原来一个比较大的测试类需要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. 然后在这些池化的对象中, 持有一个对对象池的引用, 在关闭的时候, 不进行真正的关闭处理, 而是通过调用:
_pool.returnObject(this);
或者
_pool.returnObject(_key,this);
这样一句, 将连接对象放回连接池中.
而对应的对象池前者采用的是ObjectPool, 后者是KeyedObjectPool, 因为一个数据库只对应一个连接, 而执行操作的Statement却根据Sql的不同会分很多种. 因此需要根据sql语句的不同多次进行缓存。在对连接池的管理上, common-dbcp主要采用两种对象:
一个是PoolingDriver, 另一个是PoolingDataSource, 二者的区别是PoolingDriver是一个更底层的操作类, 它持有一个连接池映射列表, 一般针对在一个jvm中要连接多个数据库, 而后者相对简单一些. 内部只能持有一个连接池, 即一个数据源对应一个连接池.
下面是common-dbcp的结构关系:
下面是参考了common-dbcp的例子之后写的一个从连接池中获取连接的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
/** * 创建连接 * * @since 2009-1-22 下午02:58:35 */ public class ConnectionUtils { // 一些common-dbcp内部定义的protocol private static final String POOL_DRIVER_KEY = "jdbc:apache:commons:dbcp:"; private static final String POLLING_DRIVER = "org.apache.commons.dbcp.PoolingDriver"; /** * 取得池化驱动器 * * @return * @throws ClassNotFoundException * @throws SQLException */ private static PoolingDriver getPoolDriver() throws ClassNotFoundException, SQLException { Class.forName(POLLING_DRIVER); return (PoolingDriver) DriverManager.getDriver(POOL_DRIVER_KEY); } /** * 销毁所有连接 * * @throws Exception */ public static void destory() throws Exception { PoolingDriver driver = getPoolDriver(); String[] names = driver.getPoolNames(); for (String name : names) { driver.getConnectionPool(name).close(); } } /** * 从连接池中获取数据库连接 */ public static Connection getConnection(TableMetaData table) throws Exception { String key = table.getConnectionKey(); PoolingDriver driver = getPoolDriver(); ObjectPool pool = null; // 这里找不到连接池会抛异常, 需要catch一下 try { pool = driver.getConnectionPool(key); } catch (Exception e) { } if (pool == null) { // 根据数据库类型构建连接工厂 ConnectionFactory connectionFactory = null; if (table.getDbAddr() != null && TableMetaData.DB_TYPE_MYSQL == table.getDbType()) { Class.forName(TableMetaData.MYSQL_DRIVER); connectionFactory = new DriverManagerConnectionFactory(table .getDBUrl(), null); } else { Class.forName(TableMetaData.ORACLE_DRIVER); connectionFactory = new DriverManagerConnectionFactory(table .getDBUrl(), table.getDbuser(), table.getDbpass()); } // 构造连接池 ObjectPool connectionPool = new GenericObjectPool(null); new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, true); // 将连接池注册到driver中 driver.registerPool(key, connectionPool); } // 从连接池中拿一个连接 return DriverManager.getConnection(POOL_DRIVER_KEY + key); } } |
------====== 本站公告 ======------
金丝燕网,一个严谨的网站!