問題起源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的時間就會不少,咱們知道在程序優化的手段中,有一個池化能夠很好的解決這個問題。 多線程
池化的概念就是先建立多個對方存在在一個容器中,當時候的時候能夠直接拿出來時候,用完後再進行歸還。 跟着這個思想,咱們來建立本身的鏈接池。性能
建立一個線程安全的容器(因爲是多線程訪問),隊列或者是list,由於Connection的對象並非有序的,因此可使用list容器測試
對Connection的對象進行封裝,增長一個isBusy
變量,每次讀取的時候就能夠選出空閒的Connection對象優化
若是取的時候,沒有可用的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; }