JDBC线程池创建与DBCP源码阅读
创建数据库连接是一个比较消耗性能的操作,同时在并发量较大的情况下创建过多的连接对服务器形成巨大的压力。对于资源的频繁分配﹑释放所造成的问题,使用连接池技术是一种比较好的解决方式。
在Java中,连接池已经有很多开源实现了,在这里使用commons-dbcp2这个包来
创建JDBC连接池:
public final class JDBCUtil{
private static DataSource myDataSource=null;
private JDBCUtil(){
}
static {
try{
Properties pro=new Properties();
InputStream is=JDBCUtil.class.getClassLoader().getResourceAsStream("mysqlPoolConf.properties");
pro.load(is);
myDataSource=BasicDataSourceFactory.createDataSource(pro);
}
catch(Exception e){
e.printStackTrace();
}
}
public static Connection getConnection()throws SQLException{
return myDataSource.getConnection();
}
public static void close(Connection conn){
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
使用很简单:
(1) 创建DataSource对象实例myDataSource
(2) 通过myDataSource的getConnection()方法获得数据库连接并使用
(3) 用完后调用连接close()方法来归还连接。
当然,这人我们不禁心存疑问,调用连接close()方法不是不是将连接关闭了吗?那连接回到连接池又是怎么实现的?一起来跟着源码看看连接池的实现过程:
首先是连接池配置文件mysqlPoolConf.properties:
为连接池的一些属性配置,这里只列举了一些常用的:
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://172.16.20.242:3306/test?characterEncoding=utf8
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=20
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#最大连接数量
maxActive=100
#是否在自动回收超时连接的时候打印连接的超时错误
logAbandoned=true
#是否自动回收超时连接
removeAbandoned=true
#超时时间(以秒数为单位)
#设置超时时间有一个要注意的地方,超时时间=现在的时间-程序中创建Connection的时间,如果 maxActive比较大,比如超过100,那么removeAbandonedTimeout可以设置长一点比如180,也就是三分钟无响应的连接进行回收,当然应用的不同设置长度也不同。
removeAbandonedTimeout=180
#<!-- 超时等待时间以毫秒为单位 -->
#maxWait代表当Connection用尽了,多久之后进行回收丢失连接
maxWait=1000
跟随源码看看连接池的实现逻辑:
BasicDataSourceFactory为一个工厂类,在调用它的方法createDataSource(Properties properties)时,通过createDataSource()创建了一个PoolingDataSource对象的实例
createDataSource()源码如下,我在源码中加了一部分注释:
protected DataSource createDataSource() throws SQLException {
if (closed) {
throw new SQLException("Data source is closed");
}
// Return the pool if we have already created it
// This is double-checked locking. This is safe since dataSource is
// volatile and the code is targeted at Java 5 onwards.
if (dataSource != null) {//检查dataSource是否已经关闭
return dataSource;
}
synchronized (this) {//同步代码块,避免多个线程来同时创建连接池
//如果dataSource已经创建,直接返回, 由于同步代码块需要获得锁,
//可能其他线程已经创建了dataSource,再释放锁,
//因此,在获得锁之后还需要再判断一下,这一点在平时编程中容易忽略。
if (dataSource != null) {
return dataSource;
}
jmxRegister();//jmx注册,不影响整体流程
// create factory which returns raw physical connections
//调用createConnectionFactory()创建JDBC连接工厂driverConnectionFactory,这个工厂使用数据库驱动来创建最底层的JDBC连接(即物理连接)
ConnectionFactory driverConnectionFactory = createConnectionFactory();
// Set up the poolable connection factory
//调用createConnectionPool()创建数据源使用的连接池,连接池顾名思义就是缓存JDBC连接的地方。
boolean success = false;
PoolableConnectionFactory poolableConnectionFactory;//连接池工厂,保存了一系列的状态信息 和 JDBC连接工厂
try {
poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
poolableConnectionFactory.setMaxOpenPrepatedStatements(maxOpenPreparedStatements);
success = true;
} catch (SQLException se) {
throw se;
} catch (RuntimeException rte) {
throw rte;
} catch (Exception ex) {
throw new SQLException("Error creating connection factory", ex);
}
if (success) {
// create a pool for our connections
//创建 GenericObjectPool<PoolableConnection>类的实例
//GenericObjectPool是apach-commons-pool包的一个通用对象池类,其构造函数参数是一个对象工厂实例
createConnectionPool(poolableConnectionFactory);
}
// Create the pooling data source to manage connections
DataSource newDataSource;
success = false;
try {
newDataSource = createDataSourceInstance();//创建了一个PoolingDataSource的实例
newDataSource.setLogWriter(logWriter);
success = true;
} catch (SQLException se) {
throw se;
} catch (RuntimeException rte) {
throw rte;
} catch (Exception ex) {
throw new SQLException("Error creating datasource", ex);
} finally {
if (!success) {
closeConnectionPool();
}
}
// If initialSize > 0, preload the pool
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();//初始化initialSize个JDBC连接
}
} catch (Exception e) {
closeConnectionPool();
throw new SQLException("Error preloading the connection pool", e);
}
// If timeBetweenEvictionRunsMillis > 0, start the pool's evictor task
startPoolMaintenance();
dataSource = newDataSource;
return dataSource;
}
}
在这一步创建了一个池化连接类工厂的实例,真正的连接是由这个工厂创建并放入连接池,GenericObjectPool是连接池的具体管理者,它是在commons-pool2这个包中实现的一个通用的对象池,GenericObjectPool负责连接池的管理(包括初始化连接,将多余的空闲连接关闭等)。之后,在使用需要使用数据库的Connection的时候,通过PoolingDataSource的getConnection() 方法获得一个连接,getConnection()方法实现如下:
public Connection getConnection() throws SQLException {
try {
//_pool是在BasicDataSource创建的GenericObjectPool<PoolableConnection> connectionPool这个实例
C conn = _pool.borrowObject();//从JDBC连接池中获得一个JDBC连接
if (conn == null) {
return null;
}
//PoolGuardConnectionWrapper是DBCP对JDBC Connection这个类的一个封装
return new PoolGuardConnectionWrapper<>(conn);
} catch(SQLException e) {
throw e;
} catch(NoSuchElementException e) {
throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLException("Cannot get a connection, general error", e);
}
}
_pool.borrowObject()得到是PoolableConnection对象的实例,其继承自DelegatingConnection。
PoolGuardConnectionWrapper继承了DelegatingConnection这个类且对父类并没有做太多的改动。DelegatingConnection实现了java.sql.Connection接口,createStatement(),prepareStatement(),commit(),rollback()这些方法和我们以往
通过DriverManager.getConnection()得到的Connection使用方法是一致的。
重点在调用colse()方法时,并不是真正的关闭了连接,而是对连接做了状态清理后将
通过GenericObjectPool的returnObject方法将其归还至连接池了,看看源码就明白了。
先看看DelegatingConnection这个类实现的close方法,主要是做了Statement和ResultSet的清理:
public void close() throws SQLException {
if (!_closed) {
closeInternal();
}
}
protected final void closeInternal() throws SQLException {
try {
passivate();
} finally {
if (_conn != null) {
try {
_conn.close();
} finally {
_closed = true;
}
} else {
_closed = true;
}
}
}
protected void passivate() throws SQLException {
// The JDBC spec requires that a Connection close any open
// Statement's when it is closed.
// DBCP-288. Not all the traced objects will be statements
//关闭 Statement and ResultSet
List<AbandonedTrace> traces = getTrace();
if(traces != null && traces.size() > 0) {
Iterator<AbandonedTrace> traceIter = traces.iterator();
while (traceIter.hasNext()) {
Object trace = traceIter.next();
if (trace instanceof Statement) {
((Statement) trace).close();
} else if (trace instanceof ResultSet) {
// DBCP-265: Need to close the result sets that are
// generated via DatabaseMetaData
((ResultSet) trace).close();
}
}
clearTrace();
}
setLastUsed(0);
}
PoolableConnection对close()方法进行了复写:
/**
* Returns me to my pool.
*/
@Override
public synchronized void close() throws SQLException {
if (isClosedInternal()) {
// already closed
return;
}
boolean isUnderlyingConectionClosed;
try {
isUnderlyingConectionClosed = getDelegateInternal().isClosed();
} catch (SQLException e) {
try {
_pool.invalidateObject(this);
} catch(IllegalStateException ise) {
passivate();//在这个方法内部还是调用的父类DelegatingConnection的passivate()方法
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING the original exception will be rethrown
}
throw new SQLException("Cannot close connection (isClosed check failed)", e);
} if (isUnderlyingConectionClosed) {
try {
_pool.invalidateObject(this);
} catch(IllegalStateException e) {
passivate();
getInnermostDelegate().close();
} catch (Exception e) {
throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
}
} else {
try {
_pool.returnObject(this);
} catch(IllegalStateException e) {
passivate();
getInnermostDelegate().close();
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLException("Cannot close connection (return to pool failed)", e);
}
}
}
@Override
protected void passivate()throws SQLException {
super.passivate();
setClosedInternal(true);
}
基本逻辑是调用父类的passivate()方法做状态清理,然后将连接归还给连接池。因此,回到本篇开始的地方,调用close()方法是不会关闭该连接的。
最后,DBCP这个包大致的框架结构如下
主要有以下几点:
1、 实现池化连接对象PoolableConnection,这个即是我们操作数据的连接对象
2、 池化连接对象工厂类PoolableConnectionFactory,池化连接对象的创建工厂
3、 BasicDataSource工厂类BasicDataSourceFactory,用以创建PoolingDataSource类的实例
4、 PoolingDataSource使用PoolableConnectionFactory创建池化连接,并使用GenericObjectPool作为池对象的管理者。
5、 由于一个connection可以对应多个statement,PoolableConnection内部使用KeyedObjectPool来管理这些statement。
个人理解,不足或者错误之处敬请指出~~~
JDBC线程池创建与DBCP源码阅读的更多相关文章
- 【转载】深度解读 java 线程池设计思想及源码实现
总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...
- 线程池 ThreadPoolExecutor 类的源码解析
线程池 ThreadPoolExecutor 类的源码解析: 1:数据结构的分析: private final BlockingQueue<Runnable> workQueue; // ...
- Java并发指南12:深度解读 java 线程池设计思想及源码实现
深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...
- 线程池 ThreadPoolExecutor 原理及源码笔记
前言 前面在学习 JUC 源码时,很多代码举例中都使用了线程池 ThreadPoolExecutor,并且在工作中也经常用到线程池,所以现在就一步一步看看,线程池的源码,了解其背后的核心原理. 公众号 ...
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)
在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...
- Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明
Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明 作者: Grey 原文地址: 博客园:Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明 C ...
- 理解线程池到走进dubbo源码
引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. ...
- 并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)
前两篇文章讲了线程池的源码分析,再来看这篇文章就比较简单了, 本文主要讲解 Executors 这个工具类,看看长江创建线程池的几种方法. newFixedThreadPool 生成一个固定大小的线程 ...
- 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)
史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,E ...
随机推荐
- CBC翻转攻击(实验吧_简单的登陆题)
题目链接 http://ctf5.shiyanbar.com/web/jiandan/index.php 有源码在test.php页面 分析代码过程 如果post id,将id转字符串,然后进入sql ...
- requests-post请求
post与get方法的区别在于post需要提交一些数据以备处理. 在requests里面非常简单,headers,data都是直接加进去就可以了 # requests.post提交表单# 有些网站使用 ...
- dev gridControl 自定义绘制列头颜色
1.添加事件CustomDrawColumnHeader private void gvw1_CustomDrawColumnHeader(object sender, DevExpress.Xtra ...
- Redis、Memcache与MongoDB的区别
>>Memcached Memcached的优点:Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key.value的字节大小以及服务器硬件性能,日常环境 ...
- MongoDB系列六(聚合).
一.概念 使用聚合框架可以对集合中的文档进行变换和组合.基本上,可以用多个构件创建一个管道(pipeline),用于对一连串的文档进行处理.这些构件包括筛选(filtering).投射(project ...
- R语言-美国枪杀案分析
案例:该数据集的是一个关于美国2017年犯罪的一个数据集,接下来我们对该数据集进行分析 字段: #### S# :数据编号 #### Location:案件发生城市,州 #### Date:时间 ## ...
- XML 解析默认去掉命名空间和注释
//创建xml文档 XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(@"C:\Users\Tony\Downloads\统一标准报文格 ...
- jqgrid表格插件总结
jqgrid整了好多天,最重要得是理清思路,故做下记录,让大家少走弯路 1 配置环境,下载几个包导入(自行百度),从官网上下载一个案例导入,可以简单显示一个静态的画面(注意json包之间的兼容性) ...
- Java8-Collect收集Stream
Collection, Collections, collect, Collector, Collectos Collection是Java集合的祖先接口. Collections是java.util ...
- ●洛谷P1903 [国家集训队]数颜色
题链: https://www.luogu.org/problemnew/show/P1903题解: 序列带修莫队, 推荐博客https://www.cnblogs.com/Paul-Guderian ...