基础介绍
数据库连接池负责分配、管理和释放数据库连接,它允许应程序重复使用一个现有的数据库连接,而不是再重新建立一个。
作用
- 资源重用 由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,增进了系统环境的平稳性(减少内存碎片以级数据库临时进程、线程的数量)
- 更快的系统响应速度 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池内备用。此时连接池的初始化操作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
- 新的资源分配手段 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接技术。
- 统一的连接管理,避免数据库连接泄露 在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用的连接,从而避免了常规数据库连接操作中可能出现的资源泄露
手写连接池步骤
基础配置
创建一个DbConfig类在其中进行数据库连接池的基础配置
public class DbConfig {
public static final String driverName = "com.mysql.jdbc.Driver"; //jdbc驱动
public static final String url = "jdbc:mysql://localhost:3306/"; //本地数据库接口
public static final String userName = "root"; //账号
public static final String password = "password"; //密码
public static final int maxConnections = 10; // 空闲池,最大连接数
public static final int initConnections = 5;// 初始化连接数
public static final long connTimeOut = 1000;// 重复获得连接的频率
public static final int maxActiveConnections = 100;// 最大允许的连接数,和数据库相匹配
public static final long connectionTimeOut = 1000 * 60 * 20;// 连接超时时间,这里为20分钟
}
千万别忘了添加数据库驱动文件,如图,不然程序跑不起来
连接池类
创建一个连接池工具接口ConnectionPool
import java.sql.Connection;
public interface ConnectionPool {
//获取连接
public Connection getConnection();
//销毁连接
public void releaseConnection(Connection connection);
}
实现该接口
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
public class ConnectionPoolImpl implements ConnectionPool {
// 空闲连接池
private List<Connection> freeConnection = new Vector<Connection>();
// 活动连接池
private List<ConnectionInfo> activeConnection = new Vector<ConnectionInfo>();
//当前连接数
private AtomicInteger countConne = new AtomicInteger(0);
//初始化
public ConnectionPoolImpl() {
init();
}
private void init() {
//初始化连接放到空闲连接池中
if (DbConfig.initConnections > 0) {
for (int i = 0; i < DbConfig.initConnections; i++) {
//加入连接到空闲连接池中
AddOneConnectToFree();
}
}
}
private void check() {
TimeCheck timeCheck = new TimeCheck();
// 延迟一个重复获得连接的频率开始检查,每一个重复获得连接的频率检查一次
new Timer().schedule(timeCheck,Long.valueOf(DbConfig.connTimeOut),Long.valueOf(DbConfig.connTimeOut));
}
class TimeCheck extends TimerTask {
public void run() {
System.out.println("例行检查...");
for (int i = 0; i < activeConnection.size(); i++) {
ConnectionInfo info = activeConnection.get(i);
// 连接获取时间(加入到活动连接池的时间)
long startTime = info.getUseStartTime();
// 获取系统当前时间
long currentTime = System.currentTimeMillis();
try {
if ((currentTime - startTime) > Long.valueOf(DbConfig.connectionTimeOut)) {
Connection conn = info.getConn();
if (conn != null && !conn.isClosed()) {
// 关闭连接
conn.close();
// 从活动连接池中移出
activeConnection.remove(i);
// 总连接数变化
countConne.decrementAndGet();
System.out.println("发现有超时连接强行关闭," + conn + ",空闲连接数:" + freeConnection.size() + "," + "在使用连接数:" + activeConnection.size() + ",总的连接数:" + countConne.get());
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
}
}
}
}
// 向空闲连接池中加入连接
private void AddOneConnectToFree(){
Connection connection = newConnection();
if (connection!=null){
// 连接可用,向空闲连接池中加入连接
freeConnection.add(connection);
}else {
// 连接不可用,递归尝试
AddOneConnectToFree();
}
}
// 新建连接
private Connection newConnection(){
try{
Class.forName(DbConfig.driverName);
Connection connection = DriverManager.getConnection(DbConfig.url,DbConfig.userName,DbConfig.password);
System.out.println("创建一个新的连接:"+connection);
return connection;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 获取连接
public synchronized Connection getConnection() {
try{
Connection connection = null;
// 判断是否大于最大连接数
if (countConne.get() < DbConfig.maxActiveConnections){
// 判断空闲连接池是否有对象
if (freeConnection.size() > 0){
// 有对象时从空闲连接池中获取一个连接
connection = freeConnection.remove(0);
}else {
// 没有对象时新建连接
connection = newConnection();
}
// 对象可用
if (connection != null && !connection.isClosed()){
// 将连接对象加入到活动连接池中
activeConnection.add(new ConnectionInfo(connection,System.currentTimeMillis()));
// 总连接数改变
countConne.getAndIncrement();
System.out.println(Thread.currentThread().getName() + ",获取并使用连接:" + connection + ",空闲连接数:" + freeConnection.size() + "," + "活动连接数:" + activeConnection.size() + ",总的连接数:" + countConne.get());
}else {
//如果对象不可用,递归重试
connection = getConnection();
}
}else {
System.out.println(Thread.currentThread().getName() + ",连接池最大连接数为:" + DbConfig.maxConnections + "已经满了,需要等待...");
//当达到最大连接时,等待后重试
wait(DbConfig.connTimeOut);
connection = getConnection();
}
return connection;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//释放连接
public synchronized void releaseConnection(Connection connection) {
try{
if (connection != null && !connection.isClosed()){
// 从活动连接池中移出
for (int i = 0; i < activeConnection.size(); i++) {
if (activeConnection.get(i).getConn() == connection) {
activeConnection.remove(i);
}
}
//判断空闲容器是否达到最大连接
if (freeConnection.size() < DbConfig.maxConnections){
//小于最大连接,则添加到空闲容器中
freeConnection.add(connection);
}else {
//大于最大连接直接关闭
connection.close();
}
// 总连接数改变
countConne.getAndDecrement();
}
System.out.println("回收了一个连接:" + connection + ",空闲连接数为:" + freeConnection.size() + ",活动连接数为:" + activeConnection.size());
// 唤醒其它线程
notifyAll();
}catch (Exception e){
e.printStackTrace();
}
}
}
活动连接池中存放的ConnectionInfo类代码
import java.sql.Connection;
public class ConnectionInfo {
private Connection conn;
private long useStartTime; //开始使用时间
public Connection getConn() {
return conn;
}
public void setConn(Connection conn) {
this.conn = conn;
}
public long getUseStartTime() {
return useStartTime;
}
public void setUseStartTime(long useStartTime) {
this.useStartTime = useStartTime;
}
public ConnectionInfo(Connection conn, long useStartTime) {
super();
this.conn = conn;
this.useStartTime = useStartTime;
}
}
测试
import java.sql.Connection;
public class Test {
public static void main(String[] args) {
// 创建连接池
ConnectionPool connectionPool = new ConnectionPoolImpl();
// 利用线程模拟获取连接
for (int i = 0; i <= 10000; i++) {
new Thread(()->{
for (int j=0;j<6;j++){
Connection connection = connectionPool.getConnection();
connectionPool.releaseConnection(connection);
}
}).start();
}
}
}
运行结果
开始
结束
为了呈现效果,我将连接池的连接数限制的更小。出现接池已满的现象
由于模拟的线程释放连接十分迅速,所以看不到超时的现象。
更改的配置
public static final int maxConnections = 5; // 空闲池,最大连接数
public static final int initConnections = 3;// 初始化连接数
public static final int maxActiveConnections = 10;// 最大允许的连接数