最开始使用数据库连接池DBCP是在公司的项目中,使用Spring+MyBatis直接简单的设置了一些参数就拿来用了。因为配置的部分也是其他同事做好的,所以对于DBCP也没有深入了解过。
后来帮同学写一点服务器代码,没有用其他任何框架只是使用DBCP数据库连接池来管理数据库连接。在这个过程中发现程序直接执行到被挂起,但是程序并没有执行完。
我使用的dbcp数据库连接池的版本是1.x,下图是我依赖的包文件的示意图
图1 dbcp版本
下面的代码只是为了还原问题的情况的代码,这是比较糟糕的代码,请不要在实际中这样写。代码只是使用BasicDataSource获得连接,而不关闭连接。
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbcp.BasicDataSource;
public class Start {
private static BasicDataSource dbcp = new BasicDataSource();
static{
dbcp.setDriverClassName("com.mysql.jdbc.Driver");
dbcp.setUsername("tim");
dbcp.setPassword("123456");
dbcp.setUrl("jdbc:mysql://localhost:3306/weibo");
}
public static boolean test()
{
return dbcp.getTestOnBorrow();
}
public static Connection getConnection()
{
try {
return dbcp.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
for(int i=0;i<10;i++)
{
Start.getConnection();
}
}
}
直接运行,发现程序不有办法运行完成,所以我打算看一下数据库的连接。我使用的是mysql数据库,在mysql的客服端执行了:
show processlist命令
图2 数据库连接进程
如上图2所示是MySQL数据库的连接,发现共有9个连接,很明显其中一个是我们使用的MySQL客户端工具,我们可以看到Info的内容是show processlist。其他8个连接应该就是我们程序打开没有关闭的连接了。这个我们会在下一篇中具体介绍,这是因为dbcp默认的最大连接数量是8。
使用jvisualvm查看了一下线程信息,发现main线程一直处于等待状况,并且只有main线程不是守护线程,其他的都是守护线程。
图3 程序运行线程情况
刚刚开始的时候怀疑是不是出现死锁的原因造成了这种情况,所以使用JConsole工具检查了一下有没有出现死锁的情况。如下图所示,首先执行了jps查看了一下java进程对应的pid,找到程序Start对应的pid 12068,执行JConsole 12068命令打开JConsole查看对应的java进程情况。
图4 jps和JConsole
如下图5所示,找到线程选项卡,单击下面的检测死锁按钮来检测java进程有没有出现死锁的情况。我们可以看到并没有检测到死锁。
图5 JConsole图形界面
我想尝试着从程序堆栈中找到一些有用的信息,于是使用jstack工具,如下图6所示执行jstack –l pid > dbcp.txt 命令导出了堆栈的信息,-l选项的意思是打印附加信息。
图6 jstack命令
如下图7所示是主线程的堆栈信息:
从堆栈信息中可以看出main线程被wait方法挂起了,是在GenericObjectPool类中的borrowObject方法上。锁是在被PoolingDataSource持有的,跟进去看源码发现还是GenericObjectPool类中的borrowObject方法上。下面来仔细看一下GenericObjectPool的borrowObject方法。
public synchronized Object borrowObject() throws Exception {
assertOpen();//首先保证连接池是打卡的
long starttime = System.currentTimeMillis();//获取当前的时间,后面计算等待时间用
for(;;) {
ObjectTimestampPair pair = null;//封装了Connection和时间戳
// _pool是一个LinkedList里面存放的ObjectTimestampPair的值是空闲连接
//空闲连接可能是开始申请的连接或者是使用之后还回的没有释放的连接
try {
pair = (ObjectTimestampPair)(_pool.removeFirst());
} catch(NoSuchElementException e) {
; /* ignored */
}
// 如果没有空闲连接,就尝试着去创建一个连接
if(null == pair) {
//如果连接最大的活跃数小于0,或者当前连接的活跃数小于最大的活跃数
//就可以创建,创建的代码在后面
if(_maxActive < 0 || _numActive < _maxActive) {
} else {
// 当数据库连接的数量已经被耗尽,则根据相应的策略来执行,
//WHEN_EXHAUSTED_GROW 是继续创建
//WHEN_EXHAUSTED_FAIL 是直接抛出异常
//WHEN_EXHAUSTED_BLOCK 是阻塞,就是等待,如果设置了maxWait,就等待maxWait,没有就等待notify
switch(_whenExhaustedAction) {
case WHEN_EXHAUSTED_GROW:
// allow new object to be created
break;
case WHEN_EXHAUSTED_FAIL:
throw new NoSuchElementException("Pool exhausted");
case WHEN_EXHAUSTED_BLOCK:
try {
if(_maxWait <= 0) {
wait();
} else {
// this code may be executed again after a notify then continue cycle
// so, need to calculate the amount of time to wait
final long elapsed = (System.currentTimeMillis() - starttime);
final long waitTime = _maxWait - elapsed;
if (waitTime > 0)
{
wait(waitTime);
}
}
} catch(InterruptedException e) {
// ignored
}
if(_maxWait > 0 && ((System.currentTimeMillis() - starttime) >= _maxWait)) {
throw new NoSuchElementException("Timeout waiting for idle object");
} else {
continue; // keep looping
}
default:
throw new IllegalArgumentException("WhenExhaustedAction property " + _whenExhaustedAction + " not recognized.");
}
}
}
//连接的活跃数量+1
_numActive++;
// 创建一个连接
boolean newlyCreated = false;
if(null == pair) {
try {
Object obj = _factory.makeObject();
pair = new ObjectTimestampPair(obj);
newlyCreated = true;
} finally {
if (!newlyCreated) {
// 创建失败,连接活跃数-1
_numActive--;
notifyAll();//活跃数减少了,通知等待monitor的线程
}
}
}
// 激活和验证连接
try {
_factory.activateObject(pair.value);
if(_testOnBorrow && !_factory.validateObject(pair.value)) {
throw new Exception("ValidateObject failed");
}
return pair.value;
}
catch (Throwable e) {
// 连接不可用
_numActive--;
notifyAll();
try {
_factory.destroyObject(pair.value);
}
catch (Throwable e2) {
// cannot destroy broken object
}
if(newlyCreated) {
throw new NoSuchElementException("Could not create a validated object, cause: " + e.getMessage());
}
else {
continue; // keep looping
}
}
}
}
从上面的代码可以发现当达到最大连接数量的时候在调用 borrowObject方法就会wait因为_whenExhausteAction的默认值是 WHEN_EXHAUSTED_BLOCK ,所以默认就是阻塞了。BasicDataSource的getCollection方法本质调用的是 borrowObject 。当活跃连接达到最大值的时候就直接阻塞了。
刚刚开始的时候我陷入了一个思维的误区,我认为既然是数据库连接池当然不能每一次使用完Connection都关闭连接。这中思维误区来自于开始的时候每一次都是使用的都是java.sql.Connection接口,通过DriverManager的getConnection方法得到JDBC4Connection,最终调用的是ConnectionImpl的close方法直接就是关闭了连接。我忽略了java.sql.Connection是一个接口的事实,而认为是本质上是对面向对象的思想理解不够。
在GenericObjectPool的borrowObject方法中创建连接的方法调用的PoolableObjectFactory中的makeObject() ,makeObject方法返回的是一个 PoolableConnection类,所以最终调用的是PoolableConnection的close方法。 PoolableConnection的close 方法的实现就是验证连接可不可用,如果可用就把连接加入到空闲连接链表中。
来源:oschina
链接:https://my.oschina.net/u/2474629/blog/690136