數據庫鏈接池原理分析及模擬實現

數據庫訪問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用的同一個鏈接,實現了資源的重複利用,沒有在去從新建立和關閉鏈接,節省了完成這些工做須要的時間,提升了效率。

相關文章
相關標籤/搜索