1 引言
对于数据库连接池,相信大家都不会陌生,当下流行的连接池也有很多(如:DBCP,C3P0等),使用连接池可以在程序中给我们带来极大的方便,也可以有效的减少创建连接给我们带来的开销,前段时间公司的项目中有用到DBCP连接池,所以在闲暇之余,自己也尝试着写了一个简易数据库连接池,当然如果对
数据库连接池
不是很懂的话,建议参考Connection Pooling with Connector/J ,详细的介绍了连接池这项技术。
2 概述
下面介绍的这个简易连接池主要包含了一下内容和功能:
- 获取连接
- 回收连接
- 验证连接的有效性(获取连接前验证)
- 定期维护连接
- 当前连接耗尽后自动增加连接数
3 源码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
public class DBPoolUtils {
private static Logger logger = Logger.getLogger(DBPoolUtils.class);
/*
* Map<Connection, String("last_call_time"-"create_time")>
*
* 当连接超过设定生命周期,或者大于设定的空闲时间则该链接自动销毁
* 本例没有实现生命周期和空闲时间管理,但是数据结构已经构造好了,需要进行生命周期管理的话,可以在
* release()方法回收时进行判断,if((当前时间-创建时间)>life_cycle_time){销毁连接} 如果不做生命周期管理,pool
* 可设计为LinkedBlockingQueue<Connection> ,不需要borrowed_queue。
*/
private static LinkedBlockingQueue<Map<Connection, String>> pool = new LinkedBlockingQueue<>();
/*
* Map<Connection,create_time>
*/
private static Map<Connection, Long> borrowed_queue = new HashMap<>();
private Integer init_pool_size = 10;
private static Integer maximum_pool_size = 21;
/*
* 计数器,记录当前已生成连接数
*/
private static AtomicInteger countor = new AtomicInteger(0);
/*
* 定期检查:最大空闲连接数,超过时,对多余连接进行回收
*/
private Integer max_idel = 30;
/*
* 定期检查:当当前空闲连接数,小于最小连接数时,新增连接,保持最小空闲连接数
*/
private Integer min_idel = 5;
private String driver_class_name = "com.mysql.jdbc.Driver";
private String driver_url = "jdbc:mysql://127.0.0.1:3306/dbname";
private String driver_user = "user";
private String driver_password = "password";
/*
* 使用ping可以进行快速验证
*/
private static final String validation_Query = "/* ping */ SELECT 1";
/*
* 1小时 毫秒数,默认连接池检测周期
*/
private long default_check_period = 60 * 60 * 1000;
/*
* 验证连接时超时时间,当连接超时时,自动生成新连接
*/
private Integer validation_Query_Timeout = 10;
// 在获取连接时验证
private boolean test_on_borrow = false;
// 每次新增连接条数
private Integer increase_number = 5;
private DBPoolUtils() {
// 初始化之前先配置数据
init();
// 设置周期性检测空闲连接
schedule_check();
}
private static class InstanceHolder {
static DBPoolUtils instance = new DBPoolUtils();
}
public static DBPoolUtils getInstance() {
return InstanceHolder.instance;
}
/**
* 获取连接
*
* @return
*/
public Connection getConnection() {
Map<Connection, String> connection_map = new HashMap<>();
Connection connecton = null;
Long create_time = 0L;
try {
connection_map = pool.poll();
// 当连接池连接已经耗尽之后重新创建新的连接
if (connection_map == null && countor.get() < maximum_pool_size) {
// 多重检查
if (pool.size() == 0 && countor.get() < maximum_pool_size) {
increase();
}
connection_map = pool.take();
}
connecton = (Connection) connection_map.keySet().toArray()[0];
create_time = Long.valueOf(connection_map.get(connecton).split("-")[1]);
borrowed_queue.put(connecton, create_time);
} catch (Exception e) {
}
// 在借出连接时检查连接是否可用
if (test_on_borrow) {
boolean states = validation_Query(connecton);
connecton = states ? connecton : getConnection();
}
return connecton;
}
/**
* 检测连接是否可用
*
* @param con
* @return
*/
private boolean validation_Query(Connection con) {
boolean tag = false;
PreparedStatement statement = null;
try {
statement = con.prepareCall(validation_Query);
statement.setQueryTimeout(validation_Query_Timeout);
statement.executeQuery();
tag = true;
} catch (SQLException e) {
tag = false;
countor.incrementAndGet();
if (con != null)
try {
con.close();
} catch (SQLException e1) {
logger.error(e1.getMessage(), e1);
}
logger.error(e.getMessage(), e);
} finally {
if (statement != null)
try {
statement.close();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
return tag;
}
/**
* 初始化连接
*/
private synchronized void init() {
Connection conn;
try {
Class.forName(driver_class_name);
while (init_pool_size-- > 0) {
conn = DriverManager.getConnection(driver_url, driver_user, driver_password);
Map<Connection, String> map = new HashMap<>();
String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
.append(String.valueOf(System.currentTimeMillis())).toString();
map.put(conn, time_str);
pool.put(map);
countor.incrementAndGet();
}
} catch (ClassNotFoundException e) {
logger.error("Failed to Load \"driver_class_name\" ,int init() method\n" + e.getMessage(), e);
} catch (SQLException e) {
logger.error(
"Failed to get connection to database ,please check configure file!if thest configure was correct configured!\n"
+ e.getMessage(),
e);
} catch (InterruptedException e) {
logger.error("Failed to put connection to pool,please check data structure!\n" + e.getMessage(), e);
}
}
/**
* 连接回收
*
* @param connection
*/
public void release(Connection connection) {
/*
* 使用一条线程来处理回收,主要是考虑到回收的效率问题,但这样可能会存在另外一个问题
* 已经释放掉的连接,未被及时放入线程池中
* 但是这样的处理方式,可以加快程序的反应速度,具体影响待测,当然理论上只要逻辑没有问
* 题,是不会产生什么实际影响。
*/
new Thread(new Runnable() {
@Override
public void run() {
try {
if (connection != null) {
long create_time = borrowed_queue.remove(connection);
String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
.append(String.valueOf(create_time)).toString();
Map<Connection, String> map = new HashMap<>();
map.put(connection, time_str);
pool.put(map);
System.out.println("重入连接池....");
}
} catch (InterruptedException e) {
logger.info(e.getMessage(), e);
}
}
}).start();
}
/**
* 连接池耗尽时,若未达最大连接数,则增加连接。
*/
private synchronized void increase() {
// 检测,如果前一个进入该方法的线程已经产生了新连接,则不做处理,否则产生因连接
if (pool.size() == 0) {
// 增加新连接
Integer current_number = 0;
/*
* 用于记录可增加的数量,如果该数量大于增长量,则用while增加,否则使用指定数量的另一个方法增加
*/
Integer tag_number = maximum_pool_size - countor.get();
if (tag_number >= increase_number)
while (countor.get() <= maximum_pool_size && current_number++ < increase_number) {
try {
Connection conn;
conn = DriverManager.getConnection(driver_url, driver_user, driver_password);
Map<Connection, String> map = new HashMap<>();
String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
.append(String.valueOf(System.currentTimeMillis())).toString();
map.put(conn, time_str);
pool.put(map);
countor.incrementAndGet();
} catch (SQLException e) {
logger.error("Failed to increase connections:\t" + e.getMessage(), e);
} catch (InterruptedException e) {
logger.error("Failed to put connection into pool:\t" + e.getMessage(), e);
}
}
else
increase(tag_number - 1);
}
}
/**
* 新增指定条数的连接
*
* @param increase_number
*/
private synchronized void increase(Integer increase_number) {
// 增加新连接
while (increase_number-- > 0) {
Connection conn;
try {
conn = DriverManager.getConnection(driver_url, driver_user, driver_password);
Map<Connection, String> map = new HashMap<>();
String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
.append(String.valueOf(System.currentTimeMillis())).toString();
map.put(conn, time_str);
pool.put(map);
countor.incrementAndGet();
} catch (SQLException e) {
logger.error("Failed to increase connections:\t" + e.getMessage(), e);
} catch (InterruptedException e) {
logger.error("Failed to put connection into pool:\t" + e.getMessage(), e);
}
}
}
/**
* 销毁所有连接 Close all connections
*/
public void close_All_Connections() {
Map<Connection, String> connection_map = new HashMap<>();
connection_map = pool.poll();
int i = 0;
System.out.println(pool.size());
while (connection_map != null) {
Connection con = (Connection) connection_map.keySet().toArray()[0];
try {
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection_map = pool.poll();
i++;
}
System.out.println("回收完毕" + i);
}
/**
* 检测空闲连接
*/
private void check_idel_connection() {
//当前大小即是空闲连接数量
Integer current_idel = pool.size();
Map<Connection, String> connection_map = new HashMap<>();
// 若当前空闲连接数小于最小空闲连接数--新增连接
if (current_idel < min_idel) {
int j = min_idel - current_idel;
increase(j);
} else if (current_idel > max_idel) {
// 若当前空闲连接数大于最大空闲连接数--关闭连接
int i = current_idel - max_idel;
connection_map = pool.poll();
while (i-- > 0) {
if (connection_map != null) {
Connection con = (Connection) connection_map.keySet().toArray()[0];
try {
if (con != null) {
con.close();
countor.incrementAndGet();
}
} catch (SQLException e) {
logger.error("Failed to close connection!/t" + e.getMessage(), e);
}
} else
break;
}
}
}
/**
* 在指定的时间间隔内检查连接状态
*/
private void schedule_check() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
check_idel_connection();
}
}, default_check_period, default_check_period);
}
}
测试类
public class Ntest {
public static Integer counter=0;
static double start=System.currentTimeMillis();
static double end=0;
public static void main(String[] args) {
int i=100000;
while(i-->0){
new Thread(new Runnable() {
Connection con=null;
@Override
public void run() {
System.out.println(this.con=DBPoolUtils.getInstance().getConnection());
DBPoolUtils.getInstance().release(this.con);
System.out.println("current:"+(++counter));
if(counter>=9999){
end=System.currentTimeMillis();
System.out.println((end-start)/1000.0);
}
}
}).start();
}
}
}
4 测试结果
测试数据:
初始化连接数:10条,最大连接数:
20条,请求线程数:10W,运行时间:11-14秒(不开启获取连接时进行连接测试)
测试数据:初始化连接数:10条,最大连接数:
20条,请求线程数:10W,运行时间:14-20秒 (开启获取连接时验证)
5 总结
这个简易线程池只实现了最基本的提供连接管理的功能(创建回收),稍微改善程序可以在此基础之上实现连接生命周期管理,对于验证连接的有效性,采取的是在获取连接前验证的方法,该方法可能会导致额外的开销,可以采用空闲时检测,或者重入池回收时检测,另外还需测试长时间运行下是否能正确管理连接池,保证连接池正确运行。More important :如果发现有什么问题,或者有建议,希望大家能告诉我,谢谢。