dbcp源码解读与对象池原理剖析

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(_key)

或: 

_pool.returnObject(_key,this)



这样一句, 将连接对象放回连接池中. 

而对应的对象池前者采用的是ObjectPool, 后者是KeyedObjectPool, 因为一个数据库只对应一个连接, 而执行操作的Statement却根据Sql的不同会分很多种. 因此需要根据sql语句的不同多次进行缓存 
在对连接池的管理上, common-dbcp主要采用两种对象: 
一个是PoolingDriver, 另一个是PoolingDataSource, 二者的区别是PoolingDriver是一个更底层的操作类, 它持有一个连接池映射列表, 一般针对在一个jvm中要连接多个数据库, 而后者相对简单一些. 内部只能持有一个连接池, 即一个数据源对应一个连接池. 
下面是common-dbcp的结构关系: 


下面是参考了common-dbcp的例子之后写的一个从连接池中获取连接的工具类


/**
 * 创建连接
 *  
 */
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);
	}
}






Apache Commons Pool实现了对象池的功能。

定义了对象的生成、销毁、激活、钝化等操作及其状态转换,并提供几个默认的对象池实现。

在讲述其实现原理前,先提一下其中有几个重要的对象:

  • PooledObject(池对象)。
  • PooledObjectFactory(池对象工厂)。
  • Object Pool(对象池)。
下面分别详细讲解它们的实现。

PooledObject(池对象)

用于封装对象(如:线程、数据库连接、TCP连接),将其包裹成可被池管理的对象。提供了两个默认的池对象实现:
  • DefaultPoolObject。用于非软引用的普通对象。
  • PooledSoftReference。用于软引用的对象。
在开发连接池、线程池等组件时,需要根据实际情况重载5个方法:startEvictionTest、endEvictionTest、allocate、deallocate和invalidate,用于在不同的场景下修改被包裹对象的内部状态。
Apache Commons Pool 源码分析 | Apache Commons Pool Source Code Analysis - 傲风 - 0与1构筑世界,程序员创造时代

PooledObject有多种状态,在不同的环节或经过处理后状态会发生变化。

状态描述
IDLE位于队列中,未使用
ALLOCATED在使用
EVICTION位于队列中,当前正在测试,可能会被回收
EVICTION_RETURN_TO_HEAD不在队列中,当前正在测试,可能会被回收。从池中借出对象时需要从队列出移除并进行测试
VALIDATION位于队列中,当前正在验证
VALIDATION_PREALLOCATION不在队列中,当前正在验证。当对象从池中被借出,在配置了testOnBorrow的情况下,对像从队列移除和进行预分配的时候会进行验证
VALIDATION_RETURN_TO_HEAD不在队列中,正在进行验证。从池中借出对象时,从队列移除对象时会先进行测试。返回到队列头部的时候应该做一次完整的验证
INVALID回收或验证失败,将销毁
ABANDONED即将无效
RETURN返还到池中

根据Apache Commons Pool2的默认实现,其状态变化如下图所示:
Apache Commons Pool2 源码分析 | Apache Commons Pool2 Source Code Analysis - 傲风 - 0与1构筑世界,程序员创造时代

PooledObjectFactory(池对象工厂)

定义了操作PooledObject实例生命周期的一些方法,PooledObjectFactory必须实现线程安全。已经有两个抽象工厂:
  • BasePooledObjectFactory。
  • BaseKeyedPooledObjectFactory。
直接继承它们实现自己的池对象工厂。
Apache Commons Pool 源码分析 | Apache Commons Pool Source Code Analysis - 傲风 - 0与1构筑世界,程序员创造时代

方法描述
makeObject用于生成一个新的ObjectPool实例
activateObject每一个钝化(passivated)的ObjectPool实例从池中借出(borrowed)前调用
validateObject可能用于从池中借出对象时,对处于激活(activated)状态的ObjectPool实例进行测试确保它是有效的。也有可能在ObjectPool实例返还池中进行钝化前调用进行测试是否有效。它只对处于激活状态的实例调用
passivateObject当ObjectPool实例返还池中的时候调用
destroyObject当ObjectPool实例从池中被清理出去丢弃的时候调用(是否根据validateObject的测试结果由具体的实现在而定)

Object Pool (对象池)

Object Pool 负责管理 PooledObject ,如:借出对象,返回对象,校验对象,有多少激活对象,有多少空闲对象。有三个默认的实现类:
  • GenericObjectPool。
  • ProsiedObjectPool。
  • SoftReferenceObjectPool。
Apache Commons Pool 源码分析 | Apache Commons Pool Source Code Analysis - 傲风 - 0与1构筑世界,程序员创造时代

方法描述
borrowObject从池中借出一个对象。要么调用PooledObjectFactory.makeObject方法创建,要么对一个空闲对象使用PooledObjectFactory.activeObject进行激活,然后使用PooledObjectFactory.validateObject方法进行验证后再返回
returnObject将一个对象返还给池。根据约定:对象必须 是使用borrowObject方法从池中借出的
invalidateObject废弃一个对象。根据约定:对象必须 是使用borrowObject方法从池中借出的。通常在对象发生了异常或其他问题时使用此方法废弃它
addObject使用工厂创建一个对象,钝化并且将它放入空闲对象池
getNumberIdle返回池中空闲的对象数量。有可能是池中可供借出对象的近似值。如果这个信息无效,返回一个负数
getNumActive返回从借出的对象数量。如果这个信息不可用,返回一个负数
clear清除池中的所有空闲对象,释放其关联的资源(可选)。清除空闲对象必须使用PooledObjectFactory.destroyObject方法
close关闭池并释放关联的资源

BorrowObject (借出对象)

下面是GenericObjectPool中borrowObject方法的逻辑实现,有阻塞式和非阻塞式两种获取对象的模式。
Apache Commons Pool2 源码分析 | Apache Commons Pool2 Source Code Analysis - 傲风 - 0与1构筑世界,程序员创造时代
 

ReturnObject (返还对象)

下面是GenericObjectPool中returnObject方法的逻辑实现,在这里实现的FIFO(先进先出)和LIFO(后进先出)。
Apache Commons Pool2 源码分析 | Apache Commons Pool2 Source Code Analysis - 傲风 - 0与1构筑世界,程序员创造时代





  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值