最开始使用数据库链接池DBCP是在公司的项目中,使用Spring+MyBatis直接简单的设置了一些参数就拿来用了。由于配置的部分也是其余同事作好的,因此对于DBCP也没有深刻了解过。java
后来帮同窗写一点服务器代码,没有用其余任何框架只是使用DBCP数据库链接池来管理数据库链接。在这个过程当中发现程序直接执行到被挂起,可是程序并无执行完。mysql
我使用的dbcp数据库链接池的版本是1.x,下图是我依赖的包文件的示意图sql
图1 dbcp版本数据库
下面的代码只是为了还原问题的状况的代码,这是比较糟糕的代码,请不要在实际中这样写。代码只是使用BasicDataSource得到链接,而不关闭链接。apache
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。oop
使用jvisualvm查看了一下线程信息,发现main线程一直处于等待情况,而且只有main线程不是守护线程,其余的都是守护线程。this
图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 方法的实现就是验证链接可不可用,若是可用就把链接加入到空闲链接链表中。