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