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 ...
随机推荐
- WPF设置控件获得焦点
1.这个比较有效 this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => { Keyboard.Foc ...
- win10的mysql服务无法启动
net start mysql 无法启动 1.可以进入MySQL的bin目录下 mysql --remove 2.检查一下自己的my.ini是否配置正确 [mysqld] #basedir代表自己My ...
- 类相关的BIF
1.>>> issubclass(C,A)#判断c是A的子类,返回真假 2.>>> isinstance(b1,B) #判断c1是B类的实例化对象,返回真假 3.& ...
- Java入门1
一.eclipse的简单使用 1.新建项目 在package explorer的空白处点击右键,新建一个项目(new->Java Project)或者点击菜单栏的File->JavaPro ...
- C#之转换两个不同类型但是成员相同的对象
/// <summary> /// 转换两个不同类型但是成员相同的对象 /// </summary> /// <typeparam name="T"& ...
- 深入解析浏览器的幕后工作原理(三) 呈现树和 DOM 树的关系
呈现树和 DOM 树的关系 呈现器是和 DOM 元素相对应的,但并非一一对应.非可视化的 DOM 元素不会插入呈现树中,例如"head"元素.如果元素的 display 属性值为& ...
- BOM,Dom 回顾
加给元素: offsetLeft(距离定位父级的距离)/offsetTop(距离定位父级的距离)/offsetWidth(可视宽度)/offHeight(可视高度) clientLeft(左边框宽度) ...
- javascript实现双向数据绑定
双向数据绑定已经是面试中经常被问到的点,需要对原理和实现都要有一定了解. 下面是实现双向绑定的两种方法: 属性劫持 脏数据检查 一.属性劫持 主要是通过Object对象的defineProperty方 ...
- [JLOI2015]城池攻占
题目描述 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池.这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,其中 fi &l ...
- 2015 多校联赛 ——HDU5402(模拟)
For each test case, in the first line, you should print the maximum sum. In the next line you should ...