寫一個數據庫鏈接池

 

 問題起源mysql

在Java訪問數據的時候,是使用JDBC驅動去建立數據庫鏈接,代碼以下:   sql

 try {
    Driver mysqlDriver = (Driver) Class.forName("com.mysql.jdbc.Driver").newInstance();
    DriverManager.registerDriver(mysqlDriver);
    Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.0.***:3306/rzframe?useSSL=false&serverTimezone=UTC", "root", "*******");
    Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery("select * from rz_user");//查詢
    connection.close();
  } catch (Exception e) {
    e.printStackTrace();
 }

 咱們對上面的代碼作一個簡單的性能測試,代碼以下:   數據庫

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 1000; i++) {
            try {
                CountDownLatch finalCountDownLatch = countDownLatch;
                Thread thread = new Thread(() -> {
                    try {
                       doJDBC();
                    } catch (Exception ex) {

                    } finally {
                        finalCountDownLatch.countDown();
                    }
                });
                thread.start();
                if (i != 0 && i % 100 == 0) {
                    countDownLatch.await();
                    System.out.println(i);
                    countDownLatch = new CountDownLatch(100);
                }
            } catch (Exception ex) {

            }
        }
        long end = System.currentTimeMillis();
        System.out.println("耗時:" + (end - start));

    }

上面代碼用了100個線程分批次去完成查詢的動做,在個人機器上運行時間45s左右。安全

從上面的代碼能夠看出問題,Connection對象每一次都是從新建立,查詢完成後,直接是調用close方法,若是不釋放,會報鏈接數過多的異常。 若是查詢屢次,那浪費在建立Connection的時間就會不少,咱們知道在程序優化的手段中,有一個池化能夠很好的解決這個問題。   多線程

池化的概念就是先建立多個對方存在在一個容器中,當時候的時候能夠直接拿出來時候,用完後再進行歸還。  跟着這個思想,咱們來建立本身的鏈接池。性能


 

編寫思路

  1. 建立一個線程安全的容器(因爲是多線程訪問),隊列或者是list,由於Connection的對象並非有序的,因此可使用list容器測試

  2. Connection的對象進行封裝,增長一個isBusy變量,每次讀取的時候就能夠選出空閒的Connection對象優化

  3. 若是取的時候,沒有可用的Connection對象,則能夠再自動建立對象,能夠自動擴容,直到擴容到容許的最大值。this

public class PooledConnection {
    private boolean isBusy=false;
    private Connection connection;

    public PooledConnection(Connection connection, boolean b) {
        this.isBusy=b;
        this.connection=connection;
    }
    public boolean isBusy() {
        return isBusy;
    }
    public void setBusy(boolean busy) {
        isBusy = busy;
    }
    public Connection getConnection() {
        return connection;
    }
    public void setConnection(Connection connection) {
        this.connection = connection;
    }
    public void close() {
        this.setBusy(false);
    }
}

在包裝好Connection後,須要考慮實現兩個方法:   spa

PooledConnection getPooledConnection();//得到一個對象

void  createPooledConnection();//建立和擴容

 爲了更好的程序調試,先定義幾個初始的參數變量:   

    //數據庫相關參數
private static String jdbcDriver = null; private static String jdbcUrl = null; private static String userName = null; private static String password = null; //容器參數 private static int initCount;//初始數量 private static int stepSize;//每次擴容的數量 private static int poolMaxSize;//最大數量 //全局鎖 private static Lock lock;

由於有多線程訪問,因此咱們採用Vector集合來做爲容器。   


 

得到對象方法      

   1. 得到對象的方法,應該是先找到一個空閒的PooledConnection變量若是有就直接返回。

   2. 若是沒有空閒的變量,則嘗試進行擴充,擴充由一個線程完成,其餘線程則等待,或者嘗試再次獲取。

 public PooledConnection getPooledConnection() throws RuntimeException, SQLException {
        PooledConnection realConnection = getRealConnection();
        while (realConnection == null) {
            if (lock.tryLock()) {//嘗試獲取鎖
                createConnections(stepSize);//只能讓一個線程擴容 得到鎖以後進行擴容
                lock.unlock();
            } else {
                try {
                    Thread.sleep(200);//線程等待
                } catch (InterruptedException e) {
                }
            }
            realConnection = getRealConnection();//再次嘗試獲取
            if (realConnection != null) {
                return realConnection;
            }
        }
        System.out.println("線程池線程數量:" + PoolsConnections.size());
        return realConnection;
    }
private PooledConnection getRealConnection() throws SQLException {
        for (PooledConnection pooledConnection : PoolsConnections) {
            try {
                if (pooledConnection.isBusy())
                    continue;
                Connection connection = pooledConnection.getConnection();
                if (!connection.isValid(200)) {//是否有效,200ms 沒有被超時
                    System.out.println("鏈接無效");
                    Connection validConnect = DriverManager.getConnection(jdbcUrl, userName, password);
                    pooledConnection.setConnection(validConnect);
                }
                pooledConnection.setBusy(true);
                return pooledConnection;
            } catch (SQLException e) {
                return null;
            }
        }
        return null;
    }

 


 

擴容方法對象       

    擴容的方法相對比較簡單,判斷當前對象數量有沒有溢出,若是沒有溢出,就進行擴容

public void createConnections(int count) throws OutofMaxCountException, IllegalArgumentException {
        if (poolMaxSize <= 0) {
            System.out.println("建立管道對象失敗,最大值參數錯誤");
            throw new IllegalArgumentException("建立管道對象失敗,最大值參數錯誤");
        }
        //判斷是否有溢出
        boolean overFlow = isOverFlow(count);
        if (overFlow) {
            return;
        }
        System.out.println("擴容");
        for (int i = 0; i < count; i++) {
            try {
                overFlow = isOverFlow(count);
                if (overFlow)
                    return;
                Connection connection = DriverManager.getConnection(jdbcUrl, userName, password);
                PooledConnection pooledConnection = new PooledConnection(connection, false);
                PoolsConnections.add(pooledConnection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        System.out.println("擴容數量:" + PoolsConnections.size());
    }

    private boolean isOverFlow(int count) {
        if (PoolsConnections.size() + count >= poolMaxSize) {
            return true;
        }
        return false;
    }

上面的代碼隱藏一個問題,咱們增長對數據的查詢方法,方便咱們測試。 查詢方法以下:

   public ResultSet querySql(String sql) {
        try {
            PooledConnection pooledConnection = getPooledConnection();
            Connection connection = pooledConnection.getConnection();
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            Thread.sleep(1000);
            pooledConnection.close();
            return resultSet;
        } catch (Exception e) {

        }
        return null;
    }

 咱們對代碼作性能測試一樣的測試,在個人電腦運行時間爲5s左右,大概快了10倍。   但通過屢次測試,代碼拋出了ConcurrentModificationException異常,這個異常的緣由是由於在使用的時候,咱們又修改了正在使用的對象。因此在使用的時候要對對象進行加一個讀寫鎖。     

爲了鎖不至於影響到鎖的性能,咱們把鎖碎片化,採用針對每個對象進行加鎖,而不是全局加鎖。修改後的封裝對象:   

public class PooledConnection {

    private boolean isBusy = false;
    private Connection connection;
    private ReentrantReadWriteLock reentrantReadWriteLock;

    public PooledConnection(Connection connection, boolean b) {
        this.connection = connection;
        reentrantReadWriteLock = new ReentrantReadWriteLock();
    }
    public boolean isBusy() {
        return isBusy;
    }

    public void setBusy(boolean busy) {
        isBusy = busy;
    }

    public Connection getConnection() {
        return connection;
    }
    public void setConnection(Connection connection) {
        this.connection = connection;
    }
    public void close() {
        this.setBusy(false);
    }
    public void shutDown() {
        try {
            this.connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

//增長讀寫鎖的操做
public void writeLock() { this.reentrantReadWriteLock.writeLock().lock(); } public void unWriteLock() { this.reentrantReadWriteLock.writeLock().unlock(); } public void readLock() { this.reentrantReadWriteLock.readLock().lock(); } public void unReadLock() { this.reentrantReadWriteLock.readLock().unlock(); } }

 最終修改後的建立和擴容,結果以下:   

   public PooledConnection getPooledConnection() throws RuntimeException, SQLException {
        if (poolMaxSize <= 0) {
            System.out.println("建立管道對象失敗,最大值參數錯誤");
            throw new IllegalArgumentException("建立管道對象失敗,最大值參數錯誤");
        }
        PooledConnection realConnection = getRealConnection();
        while (realConnection == null) {
            if (lock.tryLock()) {//嘗試獲取鎖
                createConnections(stepSize);//得到鎖以後進行擴容
                lock.unlock();
            } else {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                }
            }
            realConnection = getRealConnection();
            if (realConnection != null) {
                return realConnection;
            }
        }

        return realConnection;
    }

    private PooledConnection getRealConnection() {
        for (PooledConnection pooledConnection : PoolsConnections) {
            try {
                if (pooledConnection.isBusy())
                    continue;
                /*
                此處要保證寫的時候不能被讀取,否則會報ConcurrentModificationException異常
                 */
                pooledConnection.writeLock();//讀寫互斥,寫寫互斥
                Connection connection = pooledConnection.getConnection();
                if (!connection.isValid(200)) {//是否有效,200ms 沒有被超時
                    Connection validConnect = DriverManager.getConnection(jdbcUrl, userName, password);
                    pooledConnection.setConnection(validConnect);
                }
                pooledConnection.setBusy(true);
                pooledConnection.unWriteLock();
                return pooledConnection;
            } catch (SQLException e) {

                return null;
            }
        }
        return null;
    }
    public void createConnections(int count) throws OutofMaxCountException, IllegalArgumentException {
        if (poolMaxSize <= 0) {
            System.out.println("建立管道對象失敗,最大值參數錯誤");
            throw new IllegalArgumentException("建立管道對象失敗,最大值參數錯誤");
        }
        //判斷是否有溢出
        boolean overFlow = isOverFlow(count);
        if (overFlow) {
            return;
        }
        System.out.println("擴容");
        for (int i = 0; i < count; i++) {
            try {
                overFlow = isOverFlow(count);
                if (overFlow)
                    return;
                Connection connection = DriverManager.getConnection(jdbcUrl, userName, password);
                PooledConnection pooledConnection = new PooledConnection(connection, false);
                PoolsConnections.add(pooledConnection);
            } catch (SQLException e) {

            }
        }
        System.out.println("擴容數量:" + PoolsConnections.size());
    }

    private boolean isOverFlow(int count) {
        if (PoolsConnections.size() + count >= poolMaxSize) {
            return true;
        }
        return false;
    }

完整地址:https://u7704756.pipipan.com/fs/7704756-387364520

相關文章
相關標籤/搜索