PoolCleaner是一个定时任务,该任务在创建线程池时自动启动,该任务定期执行哪些工作呢,带着这个问题进入源码:
@Override
public void run() {
ConnectionPool pool = this.pool.get();
if (pool == null) {
stopRunning();
} else if (!pool.isClosed() &&
(System.currentTimeMillis() - lastRun) > sleepTime) {
lastRun = System.currentTimeMillis();
try {
if (pool.getPoolProperties().isRemoveAbandoned())
pool.checkAbandoned();
if (pool.getPoolProperties().getMinIdle() < pool.idle
.size())
pool.checkIdle();
if (pool.getPoolProperties().isTestWhileIdle())
pool.testAllIdle();
} catch (Exception x) {
log.error("", x);
}
}
}
而checkAbandoned、checkIdle、testAllIdle的执行是受条件约束的,比如checkAbandoned必须在连接池属性removeAbandoned配置为true时才会执行,更多tomcat jdbc连接池配置请参考官网:http://tomcat.apache.org/tomcat-8.5-doc/jdbc-pool.html
下面将一一介绍checkAbandoned、checkIdle、testAllIdle三个任务到底做了些什么工作,
/**
checkAbandoned会清除一些使用时间过长的数据库连接,
要注意的是这部分工作针对的就是使用中的数据库连接,及ConnectionPool类BlockingQueue<PooledConnection> busy属性!!
**/
public void checkAbandoned() {
try {
if (busy.size()==0) return;
Iterator<PooledConnection> locked = busy.iterator();
int sto = getPoolProperties().getSuspectTimeout();
while (locked.hasNext()) {
PooledConnection con = locked.next();
boolean setToNull = false;
try {
//操作之前加锁
con.lock();
//该连接已被移至idle队列中
if (idle.contains(con))
continue;
long time = con.getTimestamp();
long now = System.currentTimeMillis();
if (shouldAbandon() && (now - time) > con.getAbandonTimeout()) {
//从busy队列中移除该连接
busy.remove(con);
//废弃该连接
abandon(con);
setToNull = true;
} else if (sto > 0 && (now - time) > (sto*1000)) {
//将该连接列为疑似要被废弃状态并发送废弃消息
suspect(con);
} else {
//do nothing
} //end if
} finally {
con.unlock();
if (setToNull)
con = null;
}
} //while
} catch (ConcurrentModificationException e) {
log.debug("checkAbandoned failed." ,e);
} catch (Exception e) {
log.warn("checkAbandoned failed, it will be retried.",e);
}
}
protected boolean shouldAbandon() {
if (poolProperties.getAbandonWhenPercentageFull()==0) return true;
float used = busy.size();
float max = poolProperties.getMaxActive();
float perc = poolProperties.getAbandonWhenPercentageFull();
return (used/max*100f)>=perc;
}
shouldAbandon方法busy状态的连接是否可以被废弃,被废弃需要满足以下条件:
busy态连接数/最大允许存活量>最大允许的存活比例
//该任务针对的是idle队列的连接,即ConnectionPool的BlockingQueue<PooledConnection> idle属性
public void checkIdle(boolean ignoreMinSize) {
//ignoreMinSize传入值为false
try {
if (idle.size()==0) return;
long now = System.currentTimeMillis();
Iterator<PooledConnection> unlocked = idle.iterator();
//当前idle队列数目大于连接池设置的minIdle值,需要释放多余的空间连接
while ( (ignoreMinSize || (idle.size()>=getPoolProperties().getMinIdle())) && unlocked.hasNext()) {
PooledConnection con = unlocked.next();
boolean setToNull = false;
try {
con.lock();
//the con been taken out, we can't clean it up
//idle跟busy队列中数据可以互相转换,到底怎么转换的后期文章将会介绍
if (busy.contains(con))
continue;
long time = con.getTimestamp();
//具体看下面的shouldReleaseIdle方法
if (shouldReleaseIdle(now, con, time)) {
release(con);
idle.remove(con);
setToNull = true;
} else {
//do nothing
} //end if
} finally {
con.unlock();
if (setToNull)
con = null;
}
} //while
} catch (ConcurrentModificationException e) {
log.debug("checkIdle failed." ,e);
} catch (Exception e) {
log.warn("checkIdle failed, it will be retried.",e);
}
}
protected boolean shouldReleaseIdle(long now, PooledConnection con, long time) {
if (con.getConnectionVersion() < getPoolVersion()) return true;
//getReleaseTime返回的是连接池的minEvictableIdleTimeMillis配置,这个参数决定了一个连接在被废弃前所允许的最大idle时间
else return (con.getReleaseTime()>0) && ((now - time) > con.getReleaseTime()) && (getSize()>getPoolProperties().getMinIdle());
}
//在空闲期检测所有的idle队列中连接
public void testAllIdle() {
try {
if (idle.size()==0) return;
Iterator<PooledConnection> unlocked = idle.iterator();
while (unlocked.hasNext()) {
PooledConnection con = unlocked.next();
try {
con.lock();
//the con been taken out, we can't clean it up
if (busy.contains(con))
continue;
//如果该连接检验失败则可以从idle队列移除,并释放该数据库连接,由于使用数据库连接池的连接是从idle队列中获取的,为了保证连接的有效性需要定期检测这些连接,有些连接可能会被数据库服务端关闭,如果不校验就使用,很可能会使用到无效的连接!
if (!con.validate(PooledConnection.VALIDATE_IDLE)) {
idle.remove(con);
release(con);
}
} finally {
con.unlock();
}
} //while
} catch (ConcurrentModificationException e) {
log.debug("testAllIdle failed." ,e);
} catch (Exception e) {
log.warn("testAllIdle failed, it will be retried.",e);
}
}