java数据库连接池的实现
今天我们模拟实现数据库连接池的功能:
- 连接池比较典型的应用有数据库的连接池,数据库连接是一种关键的有限昂贵的资源,连接是有限的,每次新建数据库连接是很消耗性能的,所以使用数据库连接池来优化。
数据库连接池的解决方案是在应用程序启动时建立一定数量的数据库连接,这些连接组成一个连接池,由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
- 最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
- 最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
连接池的主要功能:
- 初始化
- 获取
- 释放
- 关闭
代码实现
创建数据库连接(简写)
public class MysqlUtil implements DataSource {
public static Connection getConnection(){{
try {
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/db", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
接下来实现定义核心功能接口:
public interface ConnectionPool {
/**
* 初始化线程池
* max 最大连接数
* maxWait 最大等待时间
* */
void init(int maxActive ,long maxWait);
/**
* 获取连接
* */
Connection getConnection()throws Exception;
/****
* 释放连接
* **/
void release(Connection conn)throws Exception;
}
核心功能实现类:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class ConnectionPoolImpl implements ConnectionPool {
//是否关闭(线程池关闭使用)
AtomicBoolean isClosed = new AtomicBoolean(false);
//队列实现连接 对象存储
LinkedBlockingQueue<Connection> idle; //空闲队列
LinkedBlockingQueue<Connection> busy; //繁忙队列
//大小控制连接数量
AtomicInteger activeSize = new AtomicInteger(0);
//记录连接被创建的次数
AtomicInteger createCounter = new AtomicInteger(0);
//最大连接数
int maxActive;
//最长等待时间
long maxWait;
@Override
public void init(int maxActive, long maxWait) {
this.maxActive = maxActive;
this.maxWait = maxWait;
idle = new LinkedBlockingQueue<Connection>();
busy = new LinkedBlockingQueue<Connection>();
}
@Override
public Connection getConnection() throws Exception {
Connection conn= null;
//从空闲队列中获取一个
conn= idle.poll();
if(null != conn){
//如果空闲队列里有连接,直接是被复用,再将此连接移动到busy (繁忙)队列中
busy.offer(conn);
System.out.println("从空闲队列里拿到连接");
return conn;
}
//此处不建议用synchronized锁来实现,在多线程中,锁的性能不高,没法实现并发;建议用原子类
//如果空闲队列里没有连接,就判断是否超出连接池大小,不超出就创建一个;双重判断机制实现更好,只用第二个耗性能
if(activeSize.get() < maxActive){//多线程并发
//先加再判断
if(activeSize.incrementAndGet() <= maxActive){
//创建数据库连接
conn =MysqlUtil.getConnection();
System.out.println("连接被创建的次数:" +createCounter.incrementAndGet());
//存入busy队列
busy.offer(conn );
return conn ;
}else{
//加完后超出大小再减回来
activeSize.decrementAndGet();
}
}
//如果前面2个都不能拿到连接,那就在我们设置的最大等待超时时间内,等待别人释放连接
long now = System.currentTimeMillis();//获取连接的开始时间
while(null == conn){
try {
//等待别人释放得到连接,同时也有最长的等待时间限制
conn = idle.poll(maxWait - (System.currentTimeMillis() - now), TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new Exception("等待异常 ... ");
}
if(null == conn ) {
//判断是否超时
if( (System.currentTimeMillis() - now) >= maxWait ){
throw new Exception("等待超时... ");
}else{
continue;
}
}else{
//存入busy队列
busy.offer(conn);
}
}
return conn ;
}
@Override
public void release(Connection conn) throws Exception {
if(null == conn) {
System.out.println("释放 的conn为空");
return;
}
if(busy.remove(conn)){
idle.offer(conn);
}else{
//如果释放不成功,则减去一个连接,在创建的时候可以自动补充
activeSize.decrementAndGet();
throw new Exception("释放conn异常");
}
}
}
模拟高并发,测试一下我们的连接池:
public class Test {
private final static CountDownLatch countDouwnLatch = new CountDownLatch(30); //为保证30个线程同时并发运行
public static void main(String[] args) {
final ConnectionPool pool = new ConnectionPoolImpl();
//连接池最大连接数和获取连接的超时时间
pool.init(10, 2000L);
for(int i = 0 ; i < 30; i++){//循环开30个线程
new Thread(new Runnable() {
public void run() {
countDouwnLatch.countDown();//来一个线程就减一
countDouwnLatch.await(); //此处等待状态,为了让30个线程同时进行
for(int i=0;i<10;i++){
Connction conn= null;
try {
conn= pool.getConnection();
// TODO 执行sql业务功能;这里就不多写了
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
pool.release(conn); //释放连接
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}
}
好了,数据库连接池的功能大致就是这样实现的。大家不妨自己也试试