写点什么

druid 源码学习四 - 多线程之锁探究

作者:Nick
  • 2022 年 5 月 13 日
  • 本文字数:4044 字

    阅读完需:约 13 分钟

druid源码学习四-多线程之锁探究

继续探究 DruidDataSource 核心类 init 初始化方法


        if (maxActive <= 0) {            throw new IllegalArgumentException("illegal maxActive " + maxActive);        }
if (maxActive < minIdle) { throw new IllegalArgumentException("illegal maxActive " + maxActive); }
if (getInitialSize() > maxActive) { throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive); }
if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) { throw new IllegalArgumentException("timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true"); }...
复制代码


以上对初始化对各个参数的校验,以及其他各种校验。


    initCheck();    initValidConnectionChecker();    validationQueryCheck();
复制代码


看下本人曾做过的一个项目中使用 druid 的配置,这里配置了很多参数。


  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">    <property name="url" value="${db.url}" />    <property name="username" value="${db.username}" />    <property name="password" value="${db.password}" />    <property name="filters" value="config,wall,stat"/>    <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${db.publickey}" />    <!-- 配置初始化大小、最小、最大 -->    <property name="initialSize" value="${db.initialSize}" />    <property name="minIdle" value="${db.minIdle}" />    <property name="maxActive" value="${db.maxActive}" />    <!-- 配置获取连接等待超时的时间 -->    <property name="maxWait" value="${db.maxWait}" />    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->    <property name="timeBetweenEvictionRunsMillis" value="${db.timeBetweenEvictionRunsMillis}" />    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->    <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}" />    <property name="testWhileIdle" value="true" />    <property name="validationQuery" value="select 1 from dual" />    <!-- 检测连接是否有效的超时时间 单位:秒 -->    <property name="validationQueryTimeout" value="${db.validationQueryTimeout}" />    <!-- 这里建议配置为TRUE,防止取到的连接不可用 -->    <property name="testOnBorrow" value="true" />    <property name="testOnReturn" value="false" />    <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->    <property name="poolPreparedStatements" value="true" />    <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />    <!-- 这里配置提交方式,默认就是TRUE,可以不用配置 -->    <property name="defaultAutoCommit" value="true" />    <!-- filters:统计监控信息,value是一个string,支持组合配置,如stat,log4j,那么问题来了,如果自定义的filter该如何配置呢,可以通过bean的形式添加 -->    <property name="proxyFilters">      <list>        <ref bean="stat-filter"/>      </list>    </property>    <!-- 配置removeAbandoned对性能会有一些影响,建议怀疑存在泄漏之后再打开 -->    <property name="removeAbandoned" value="true" /> <!-- 打开removeAbandoned功能 -->    <property name="removeAbandonedTimeout" value="180" /> <!-- 180秒,也就是3分钟 -->    <property name="logAbandoned" value="true" /> <!-- 关闭abanded连接时输出错误日志 -->  </bean>
复制代码


而且还做了数据库版本的监测 initCheck(),可见 druid 是不支持 oracle 10 以下版本的


   protected void initCheck() throws SQLException {        DbType dbType = DbType.of(this.dbTypeName);
if (dbType == DbType.oracle) { isOracle = true;
if (driver.getMajorVersion() < 10) { throw new SQLException("not support oracle driver " + driver.getMajorVersion() + "." + driver.getMinorVersion()); }
if (driver.getMajorVersion() == 10 && isUseOracleImplicitCache()) { this.getConnectProperties().setProperty("oracle.jdbc.FreeMemoryOnEnterImplicitCache", "true"); }
oracleValidationQueryCheck(); } else if (dbType == DbType.db2) { db2ValidationQueryCheck(); } else if (dbType == DbType.mysql || JdbcUtils.MYSQL_DRIVER_6.equals(this.driverClass)) { isMySql = true; }
if (removeAbandoned) { LOG.warn("removeAbandoned is true, not use in production."); } }
复制代码


接着继续看代码,会看到 JdbcDataSourceStat 类


            if (isUseGlobalDataSourceStat()) {                dataSourceStat = JdbcDataSourceStat.getGlobal();                if (dataSourceStat == null) {                    dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbTypeName);                    JdbcDataSourceStat.setGlobal(dataSourceStat);                }                if (dataSourceStat.getDbType() == null) {                    dataSourceStat.setDbType(this.dbTypeName);                }            } else {                dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbTypeName, this.connectProperties);            }            dataSourceStat.setResetStatEnable(this.resetStatEnable);
复制代码


然后发现另一个线程安全类 ReentrantReadWriteLock。


    private ReentrantReadWriteLock  lock  = new ReentrantReadWriteLock();
public void setMaxSqlSize(int value) { if (value == this.maxSqlSize) { return; }
lock.writeLock().lock();//写锁 try { if (value < this.maxSqlSize) { int removeCount = this.maxSqlSize - value; Iterator<Map.Entry<String, JdbcSqlStat>> iter = sqlStatMap.entrySet().iterator(); while (iter.hasNext()) { iter.next(); if (removeCount > 0) { iter.remove(); removeCount--; } else { break; } } } this.maxSqlSize = value; } finally { lock.writeLock().unlock();//解锁 } }
复制代码


经过一顿搜索了解到:之前研究的 ReentrantLock 锁,然后又来一个 ReentrantReadWriteLock。

其实,解决线程安全问题使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,当只有一个线程可以获取该锁,而实际中会有写少读多的场景,显然 ReentrantLock 满足不了这个需求,所以 ReentrantReadWriteLock 应运而生。


ReentrantReadWriteLock 采用读写分离的策略,允许多个线程可以同时获取读锁。读写锁内部维护了两个锁,一个用于读操作,一个用于写操作。所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。


可见该处:lock.writeLock().lock(); 是写锁,既然有 writeLock,那就再看看有没有 readLock 吧,果然。


JdbcDataSourceStat 类 getSqlStat() 方法使用了一个读锁。


    public JdbcSqlStat getSqlStat(long id) {        lock.readLock().lock();//读锁        try {            for (Map.Entry<String, JdbcSqlStat> entry : this.sqlStatMap.entrySet()) {                if (entry.getValue().getId() == id) {                    return entry.getValue();                }            }            return null;        } finally {            lock.readLock().unlock();//解锁        }    }
复制代码


继续看初始化方法,再往下看会发现



connections = new DruidConnectionHolder[maxActive]; evictConnections = new DruidConnectionHolder[maxActive]; keepAliveConnections = new DruidConnectionHolder[maxActive];
复制代码


DruidConnectionHolder 类,该类以 Holder 结尾,初步可以断定该类就是 Connection 的持有者或者说是所有者。我们继续多线程学习研究这条线会发现,该类也使用了之前研究的 ReentrantLock 锁。


    public void addTrace(DruidPooledStatement stmt) {        lock.lock();        try {            statementTrace.add(stmt);        } finally {            lock.unlock();        }    }
public void removeTrace(DruidPooledStatement stmt) { lock.lock(); try { statementTrace.remove(stmt); } finally { lock.unlock(); } }
复制代码


总结下,锁的使用一般都可以使用 ReentrantLock,当出现读多写少多时候可以使用 ReentrantReadWriteLock,锁的位置一般都在方法体内,范围都尽量小,而且出现 lock 的地方必然会有 unlock。

发布于: 刚刚阅读数: 3
用户头像

Nick

关注

终身学习,向死而生 2020.03.18 加入

得到、极客时间重度学习者,来infoQ 是为了输出倒逼输入

评论

发布
暂无评论
druid源码学习四-多线程之锁探究_Apache Druid_Nick_InfoQ写作社区