DruidDataSource
的 init()
方法虽说只是初始化,将初始的连接创建,放到连接池里,供应用调用。但它不仅仅只有连接的初始化,还有一些需要一直运行或者周期性执行的任务初始化,我们来看一下这块的实现。
public void init() throws SQLException {
if (inited) {
return;
}
、、、、、、、、、、、、、、、、、省略
createAndLogThread();
createAndStartCreatorThread();
createAndStartDestroyThread();
、、、、、、、、、、、、、、、、、、省略
}
他们实现主要在这三个createXXX()
里面,首先看看第一个,从名字可以看出是日志相关的Thread,内部实现如下:
private void createAndLogThread() {
if (this.timeBetweenLogStatsMillis <= 0) {
return;
}
String threadName = "Druid-ConnectionPool-Log-" + System.identityHashCode(this);
logStatsThread = new LogStatsThread(threadName);
logStatsThread.start();
this.resetStatEnable = false;
}
这个线程只有设置了timeBetweenLogStatsMillis
时间之后才会生效,会周期性的进行日志记录,关键代码是这个:
public DruidDataSourceStatValue getStatValueAndReset() {
DruidDataSourceStatValue value = new DruidDataSourceStatValue();
lock.lock();
try {
value.setPoolingCount(this.poolingCount);
value.setPoolingPeak(this.poolingPeak);
value.setPoolingPeakTime(this.poolingPeakTime);
value.setActiveCount(this.activeCount);
value.setActivePeak(this.activePeak);
value.setActivePeakTime(this.activePeakTime);
value.setConnectCount(this.connectCount);
value.setCloseCount(this.closeCount);
value.setWaitThreadCount(lock.getWaitQueueLength(notEmpty));
value.setNotEmptyWaitCount(this.notEmptyWaitCount);
value.setNotEmptyWaitNanos(this.notEmptyWaitNanos);
value.setKeepAliveCheckCount(this.keepAliveCheckCount);
// reset
this.poolingPeak = 0;
this.poolingPeakTime = 0;
this.activePeak = 0;
this.activePeakTime = 0;
this.connectCount = 0;
this.closeCount = 0;
this.keepAliveCheckCount = 0;
this.notEmptyWaitCount = 0;
this.notEmptyWaitNanos = 0;
} finally {
lock.unlock();
}
会将各种当前的数据写到DruidDataSourceStatValue
对象里,然后去重置数据,并将日志输出。
注意这是一个守护线程,其优点是当jvm结束的时候,不需要等待线程结束。整个程序关闭对线程是毫无依赖的。
接下来是createAndStartCreatorThread()
方法,它主要用来创建线程,分为两种情况:
异步创建
当用户设置了createScheduler
(异步)时,这个方法其实什么都不会做,只是对initedLatch
减一。
protected void createAndStartCreatorThread() {
if (createScheduler == null) {//只针对没有设置createScheduler的情况生效
String threadName = "Druid-ConnectionPool-Create-" + System.identityHashCode(this);
createConnectionThread = new CreateConnectionThread(threadName);
createConnectionThread.start();
return;
}
initedLatch.countDown();
}
那异步情况下主要通过createScheduler
周期性的去创建连接,创建连接代码在CreateConnectionTask
的runInternal()
方法里,这块代码里面有很多逻辑判断,包括尝试重连的代码也在里面
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& (!(keepAlive && activeCount + poolingCount < minIdle)) // 在keepAlive场景不能放弃创建
&& (!initTask) // 线程池初始化时的任务不能放弃创建
&& !isFailContinuous() // failContinuous时不能放弃创建,否则会无法创建线程
&& !isOnFatalError() // onFatalError时不能放弃创建,否则会无法创建线程
) {
clearCreateTask(taskId);
return;
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
clearCreateTask(taskId);
return;
}
}
只有当通过上述条件时,才会进行连接创建。
同步创建
同步代码核心在CreateConnectionThread
的run()
方法里,注意这里的CreateConnectionThread
是个守护线程,如果程序关闭它是不会在执行的。
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& (!(keepAlive && activeCount + poolingCount < minIdle))
&& !isFailContinuous()
) {
empty.await();
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
细心的朋友应该看出来了,同步和异步这两块代码十分类似,只是同步的判断会比异步少一点,异步情况会复杂点。然后同步和异步的线程名字其实很类似,只是后缀不一样而已,同步是Thread后缀,异步是Task后缀。一般线程池都是用的task,可能和这种命名习惯有关。
createAndStartDestroyThread()
的实现相对复杂点,会有收缩和连接丢弃,保活等逻辑,我们明天继续。