数据库链接池原理分析及模拟实现

数据库访问mysql

​ 访问数据库主要有如下几个步骤:sql

  1. 加载数据库驱动
  2. 建立数据库链接
  3. 执行访问操做并处理执行结果
  4. 关闭链接,释放资源

​ 在每一次请求数据库都要经历上述过程,建立链接和释放资源也都是些重复性的动做,当请求量比较大时,资源是个很大的浪费。如何优化呢,可使用链接池。数据库

链接池并发

​ 数据库链接池负责分配、管理和释放数据库链接,它容许应用程序重复使用一个现有的数据库链接,而不是再从新创建一个;释放空闲时间超过最大空闲时间的数据库链接来避免由于没有释放数据库链接而引发的数据库链接遗漏。测试

原理分析优化

​ 数据库链接是访问数据库必须的,能够在系统初始化时提早建立必定数量的链接,保存起来,当有建立链接的请求过来时,就直接拿出来,标记为使用中(避免与其余请求拿到同一个),使用完后,再放回链接池中。过程以下this

  1. 系统在启动时初始化链接池;
  2. 向链接池请求可用的数据库链接;
  3. 若是没有获取到可用的数据库链接,而且链接池中链接的数量小于最大链接数,则按照规定的步长给链接池中添加链接,而后再获取,若是链接池中的数量已经到了最大链接数尚未获取到可用的链接,则等待其余请求释放了链接后再获取;
  4. 使用获取到的数据库链接请求数据库;
  5. 将数据库链接放回链接池,供其余链接使用;

简单模拟实现url

/**
 * 链接池对象
 */
public class Pool {

    private String driver = null;//数据库驱动
    private String url = null;//链接地址
    private String username = null;//用户
    private String password = null;//密码

    //初始化链接数
    private static int initSize = 2;
    //池中最大链接数
    private static int maxSize = 5;
    //每次建立的链接数
    private static int stepSize = 2;
    //超时时间
    private static int timeout = 2000;

    //用来保存建立的数据库链接
    private List<PooledConnection> connectionPool = new ArrayList<PooledConnection>();

    private Lock lock = new ReentrantLock();

    public Pool(String driver, String url, String username, String password) throws Exception {
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;

        //建立链接池时初始化initSize个数据库链接放入池中
        resizePool(initSize);
    }

    /**
     * 初始化链接池
     * @param num 初始时按照initSize给池中添加链接,其余时候按照stepSize给池中加
     */
    private void resizePool(int num) throws Exception {
        //池中现有的链接数
        int currentNum = connectionPool.size();
        //池中的链接数不能超过设置的最大链接数
        if (maxSize < currentNum + num) {
            num = maxSize - currentNum;
        }
        //建立链接放入池中
        for(int i=0; i<num; i++){
            PooledConnection conn = newPooledConnection();
            connectionPool.add(conn);
        }
    }

    /**
     * 建立链接池中的链接对象,包含了状态(忙、闲)
     * @return
     */
    private PooledConnection newPooledConnection() throws Exception {
        Connection conn = createConnection();//数据库链接
        PooledConnection pconn = new PooledConnection(conn);//链接池中的链接
        return pconn;
    }

    /**
     * 建立数据库链接
     * @return
     * @throws SQLException
     */
    private Connection createConnection() throws Exception {
        //加载驱动
        this.getClass().getClassLoader().loadClass(driver);
        //建立链接
        Connection conn = null;
        conn = DriverManager.getConnection(url, username, password);
        return conn;
    }

    /**
     * 获取数据库链接
     * @return
     */
    public synchronized Connection getConnection() throws Exception {
        Connection conn = null;
        //从链接池中获取链接
        if(connectionPool.size() > 0){
            //获取一个空闲的数据库链接
            conn = getFreeConnFromPool();
            //没有获取到链接
            while(conn == null){
                //隔2秒 从新获取
                System.out.println(Thread.currentThread().getName() + " 等待获取链接");
                Thread.sleep(2000);
                conn = getFreeConnFromPool();
            }
        }
        return conn;
    }

    /**
     * 从链接池中获取空闲的链接
     * @return
     */
    private Connection getFreeConnFromPool() throws Exception {
        Connection conn = null;
        //获取可用的链接
        conn = findAvailableConn();
        //没有获取到可用的链接
        if(conn == null){
            //从新添加数据库链接到链接池中
            resizePool(stepSize);
            //获取可用的链接
            conn = findAvailableConn();
        }
        return conn;
    }

    /**
     * 获取一个可用的链接
     * @return
     * @throws Exception
     */
    private Connection findAvailableConn() throws Exception {
        Connection conn = null;
        if(connectionPool.size() > 0){
            for(PooledConnection cip : connectionPool){
                if(!cip.isBusy()){
                    conn = cip.getConn();
                    cip.setBusy(true);//获取后将当前链接状态标记为 执行
                    //判断当前链接是否可用
                    if(!conn.isValid(timeout)){
                        //conn.isValid若是链接未关闭且有效,则返回true
                        //当前链接池链接的数据库链接有问题,建立一个新的数据库链接代替它
                        conn = createConnection();
                        cip.setConn(conn);
                    }
                    break;
                }
            }
        }
        return conn;
    }

    /**
     * 把链接返回链接池
     * 把链接返回给链接池就是把状态标记为‘闲’,可让其余请求使用
     */
    public void returnConnToPool(Connection conn){
        for (PooledConnection cip : connectionPool) {
            if (cip.getConn() == conn) {
                cip.setBusy(false);//设置为空闲
                System.out.println(Thread.currentThread().getName() + " 释放了链接");
                break;
            }
        }
    }

}
/**
 * 链接池中的链接对象
 */
public class PooledConnection {
    //数据库链接
    private Connection conn;
    //用于标识当前数据库链接的状态 true:执行   false:空闲
    private boolean busy;

    public PooledConnection(Connection conn) {
        this.conn = conn;
    }

    // 此处省略get set方法
}

测试线程

public class App {
    public static void main(String[] args) throws Exception {
        //建立一个链接池
        Pool pool = new Pool("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/test",
                "root", "123456");
        //建立7个线程,模拟并发
        Thread[] threads = new Thread[7];
        for(int i=0;i<threads.length;i++){
            int t = i * 1000;
            threads[i] = new Thread(()->{
                Connection conn = null;
                try {
                    conn = pool.getConnection();
                    if(conn != null){
                        System.out.println(Thread.currentThread().getName()+"获取到链接 "+conn);
                        Thread.sleep(3000 + t);//模拟每一个链接使用时间不等
                        pool.returnConnToPool(conn);
                    }else{
                        System.out.println(Thread.currentThread().getName()+" 没有获取到链接");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "Thread-"+i);
        }

        for(Thread thread : threads){
            thread.start();
        }
    }
}

测试结果code

​ 能够看出,请求数超过池中的最大数时,多余的请求会进入等待状态,等到其余的链接被释放后才会获取到链接,线程0和线程5用的同一个链接,线程1和6用的同一个链接,实现了资源的重复利用,没有在去从新建立和关闭链接,节省了完成这些工做须要的时间,提升了效率。

相关文章
相关标签/搜索