一 序:
因为一个偶发的线上数据库报警,使用的mybatis+druid
Caused by: java.sql.SQLException: No operations allowed after statement closed.
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:964)at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860)
at com.mysql.jdbc.StatementImpl.checkClosed(StatementImpl.java:436)
at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3334)
at com.mysql.jdbc.PreparedStatement.setLong(PreparedStatement.java:3362)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.setLong(DruidPooledPreparedStatement.java:321)
at org.apache.ibatis.type.LongTypeHandler.setNonNullParameter(LongTypeHandler.java:31)
at org.apache.ibatis.type.LongTypeHandler.setNonNullParameter(LongTypeHandler.java:26)
at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:53)
***************************************************
原因未知,参数也配置了,怀疑是使用的druid版本1.0.27有bug,导致链接超时了没有关闭释放。
等下个版本上线更新下druid版本验证看看。
看了下druid的源码,比较多,终点关心连接池部分。
二 DruidDataSource
这个类比较大,3000多行,关注下init
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
throw new SQLException("interrupt", e);
}
使用了锁,没有用同步:
下面的是参数设置及校验
initCheck(); //判断数据库类型,这里关注是mysql
initExceptionSorter(); //异常容器
initValidConnectionChecker(); //根据driver初始化这里关注是MySqlValidConnectionChecker
validationQueryCheck(); //校验配置validationquery
dataSourceStat //统计相关参数设置
connections = new DruidConnectionHolder[maxActive]; //新建连接池
evictConnections = new DruidConnectionHolder[maxActive];
keepAliveConnections = new DruidConnectionHolder[maxActive];
if (!asyncInit) {
try {
// init connections
for (int i = 0, size = getInitialSize(); i < size; ++i) {
//创建物理连接(包含了参数赋值,创建物理连接,初始化连接,连接有效性校验)
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount] = holder;//放入连接池
incrementPoolingCount();//计数增加
}
if (poolingCount > 0) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
connectError = ex;
}
}
createAndLogThread(); // 日志线程,一直运行,间隔timeBetweenLogStatsMillis
createAndStartCreatorThread(); //创建连接的线程CreateConnectionThread,一直运行,连接池满了就等
createAndStartDestroyThread();//创建销毁的线程DestroyConnectionThread,一直运行,核心方法removeAbandoned
initedLatch.await();//主线程在计数器为0前一直等待。
init = true;
if (keepAlive) { //keepalive是1.0.29版本开始加入的,初始化连接池时会填充到minIdle数量,就是这里提现的。当然空闲检测也有相关代码
// async fill to minIdle
if (createScheduler != null) {
for (int i = 0; i < minIdle; ++i) {
createTaskCount++;
CreateConnectionTask task = new CreateConnectionTask();
this.createSchedulerFuture = createScheduler.submit(task);
}
} else {
this.emptySignal();
}
}
************
相关知识点:
private final CountDownLatch initedLatch = new CountDownLatch(2);
就叫倒计时同步器。当前同步数为2,在变成0后,主线程才能运行,否则一直等待中。
在创建连接与收缩池子的线程中都有initedLatch.countDown();
demo:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
static CountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("a");
c.countDown();
System.out.println("b");
c.countDown();
}
}).start();
c.await();
System.out.println("c");
}
}
输出:a
b
c
CountDownLatch底层的实现是sync.也是AQS的子类:
private static final class Sync extends AbstractQueuedSynchronizer {
可以取看下对应的代码,AQS实现的很巧妙,依赖队列的头结点,挂起,唤醒等实现了共享锁传播。
共享锁是否可以被获取的判断为空方法,交由子类(sync)去实现。
当然在创建物理连接时:
protected static AtomicIntegerFieldUpdater<DruidAbstractDataSource> createCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "creatingCount");
这里使用了atomic包的新子类,补充下相关类:
原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。
如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型