问题起源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; }