继续探究 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。
评论