【druid源码解读】-Druid-源码实践实现数据源版本V0.1.1的多线程

127 阅读1分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

Druid-源码实践实现一个简单的数据源版本V0.1

  • 实现获取连接的时候init数据源
  • 实现判断池中连接数量小于最小数量循环创建连接
  • 实现创建连接的简单查询

WmyDataSource

/**
 * @author MengyuWu
 * @version 1.0.0
 * @ClassName WmyDataSource.java
 * @Description 重新写自己的数据源
 * @createTime 2021年11月16日 19:35:00
 */
@Slf4j
public class WmyDataSource implements DataSource {
    // 定义最大连接数
    private volatile int  maxConnection = 10;
    // 定义最小连接数
    private volatile int  minConnection = 3;
    // 正在使用的连接 线程安全的队列
    private ConcurrentLinkedQueue<WmyPooledConnection> runningConnection = new ConcurrentLinkedQueue<>();
    // 空闲的连接 线程安全的队列
    private ConcurrentLinkedQueue<WmyPooledConnection> freeConnection = new ConcurrentLinkedQueue<>();
    
    protected ReentrantLock    lock = new ReentrantLock();
    //池中的连接数量
    private int  poolingCount;
    //是否初始化标记
    protected volatile boolean  inited     = false;
    private String username;
    private String url;
    private String password;

    public WmyDataSource( String url, String username,String password) {
        this.username = username;
        this.url = url;
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        init();
        // 空闲队列为空
        if(freeConnection.isEmpty()){
            // 创建连接
            WmyPooledConnection wmyPooledConnection = new WmyPooledConnection(username, url, password, this);
            runningConnection.add(wmyPooledConnection);
            return wmyPooledConnection.getConnection();
        }
        //不为空直接取
        WmyPooledConnection poll = freeConnection.poll();
        runningConnection.add(poll);
        return poll.getConnection();
    }

    private void init() throws SQLException {
        if(inited){
            return;
        }
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }
        //池中的数量小于最小数量  创建连接
        try{
            if(poolingCount < minConnection){
                // 循环创建连接 poolingCount的数值处理TODO
                for(int i=0;i<=minConnection-poolingCount;i++){
                    WmyPooledConnection wmyPooledConnection = new WmyPooledConnection(username, url, password, this);
                    freeConnection.add(wmyPooledConnection);
                }
            }
        }catch (SQLException e){
            log.error("init datasource error, url: " + this.url, e);
        }finally {
            inited = true;
            lock.unlock();
        }


    }
		......

}

WmyConnectPool

/**
 * @author MengyuWu
 * @version 1.0.0
 * @ClassName WmyConnectPool.java
 * @Description 自定义连接池
 * @createTime 2021年11月16日 20:01:00
 */
public class WmyPooledConnection implements PooledConnection, Connection {
    private String username;
    private String url;
    private String password;

    private Connection connection;
    private WmyDataSource dataSource;
		// 初始化
    public WmyPooledConnection(String username, String url, String password,  WmyDataSource dataSource) throws SQLException {
        this.username = username;
        this.url = url;
        this.password = password;
        this.dataSource = dataSource;
        this.getConnection();
    }
    ......

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return connection.prepareStatement(sql);
    }

    ......
}

Test

/**
 * @author MengyuWu
 * @version 1.0.0
 * @ClassName TestDataSource.java
 * @Description 测试数据源
 * @createTime 2021年11月16日 19:10:00
 */
public class TestDataSource {
    private static String url = "jdbc:mysql://localhost:3306/jk_test?characterEncoding=utf-8";
    private static String user = "root";
    private static String password = "12345678";
    public static void main(String[] args)  {
        long startTime = System.currentTimeMillis();
        for(int i=0;i<10;i++){
        //原始jdbc
            execuByJDBC();
        }
        System.out.println("select by jdbc ===={}"+(System.currentTimeMillis()-startTime));
        startTime = System.currentTimeMillis();
        for(int i=0;i<10;i++){
        //自己封装的数据源
            execuByWmyDatasource();
        }
        System.out.println("select by wmyDataSource ===={}"+(System.currentTimeMillis()-startTime));

    }
    private static void execuByWmyDatasource() {
        WmyDataSource dataSource = new WmyDataSource(url, user, password);
        Connection connection = null;
        String sql = "select * from jk_goods";
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = dataSource.getConnection();
            ps = connection.prepareStatement(sql);
            rs = ps.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            try {
                ps.close();
                rs.close();
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static void execuByJDBC() {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.加载驱动程序
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获得数据库链接
            conn = DriverManager.getConnection(url, user, password);
            String sql = "select * from jk_goods";
            //3.通过数据库的连接操作数据库,实现增删改查
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();

        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                ps.close();
                rs.close();
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

结果

select by jdbc ===={}1683
select by wmyDataSource ===={}1487

总结

通过对之前源码的解读进行了自定义数据源的尝试,实现自定义数据源连接和执行sql的操作,这次实现的是0.1版本,后续针对自己对holder的理解,增加holder层的处理。在实际编写自定义数据源的实践过程中,才发现之前自己看的有点太粗了,很多细节没有注意到,正好在完善自定义数据源的过程中再复习巩固一次。

Druid-源码实践实现数据源版本V0.1.1的多线程

  • 实现init的时候开启一个守护销毁线程

  • 实现守护销毁线程根据目前数据源连接数量大于最大连接数的时候放在销毁队列

init方法的时候创建守护线程

//池中的数量小于最小数量  创建连接
try{
    if(poolingCount < minConnection){
        for(int i=0;i<=minConnection-poolingCount;i++){
            WmyPooledConnection wmyPooledConnection = new WmyPooledConnection(username, url, password, this);
            freeConnection.add(wmyPooledConnection);
        }
    }
    //  创建守护线程 监听
    String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
    destroyConnectionThread = new DestroyConnectionThread(threadName);
    destroyConnectionThread.start();
}catch (SQLException e){
    log.error("init datasource error, url: " + this.url, e);
}finally {
    inited = true;
    lock.unlock();
}

DestroyConnectionThread守护线程

public class DestroyConnectionThread extends Thread {

    public DestroyConnectionThread(String name){
        super(name);
        this.setDaemon(true);
    }

    @Override
    public void run() {
        for (;;) {
            // 从前面开始删除
            try {
                // 延续druid 的设计思路 等待间隔
                if (timeBetweenEvictionRunsMillis > 0) {
                    //间隔等待
                    Thread.sleep(timeBetweenEvictionRunsMillis);
                } else {
                    Thread.sleep(1000); // 默认
                }

                if (Thread.interrupted()) {
                    break;
                }
                //调用关闭方法
                closeRun();
            } catch (InterruptedException e) {
                break;
            }
        }
    }

}


closeRun方法

public void closeRun() {
    try {
        //获取锁
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        return;
    }

    try {
        if (!inited) {
            //线程池没有初始化的话 直接返回
            return;
        }
        //需要注销的连接
       int killNum = 0;
       if(runningConnection.size()>maxConnection){
           killNum = maxConnection - runningConnection.size();
       }
       // 从正在运行的connect中获取需要销毁的线程
       for(int i=0;i<killNum;i++){
           killConnection.add(runningConnection.poll());
       }
    } finally {
        lock.unlock();
    }
    try {
        for(WmyPooledConnection connection : killConnection){
           //判断当前的连接数量
            if(connection.getConnectNum()==0){
                //正在用这个连接池的数量为0的时候 kill
                connection.close();
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

总结

今天在实际去实现销毁连接的时候,会陷入怪圈,总感觉考虑的不够周全,总觉得要加一个这个限制,加一个那个限制导致过程中逻辑加上然后又删除修改。后来越做越乱,后面重新整理思路,应该基于上个版本实现版本V.0.1.1版本即可。后面在细化整理druid连接池的方案后一步步完善。