写一个数据库链接池

 

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

相关文章
相关标签/搜索