文档章节

记一次dbcp数据库连接池问题分析

trayvon
 trayvon
发布于 2016/06/12 20:26
字数 1734
阅读 375
收藏 8

   最开始使用数据库连接池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方法上。下面来仔细看一下GenericObjectPoolborrowObject方法。
 

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接口,通过DriverManagergetConnection方法得到JDBC4Connection,最终调用的是ConnectionImplclose方法直接就是关闭了连接。我忽略了java.sql.Connection是一个接口的事实,而认为是本质上是对面向对象的思想理解不够。

  在GenericObjectPoolborrowObject方法中创建连接的方法调用的PoolableObjectFactory中的makeObject() makeObject方法返回的是一个  PoolableConnection类,所以最终调用的是PoolableConnectionclose方法。 PoolableConnectionclose 方法的实现就是验证连接可不可用,如果可用就把连接加入到空闲连接链表中。

 

© 著作权归作者所有

共有 人打赏支持
trayvon
粉丝 16
博文 125
码字总数 185343
作品 1
程序员
私信 提问
dbcp中几个重要实现类之间的关系和连接池参数简介

如下图1所示主要分析的是BasicDataSource、GenericObjectPool、DriverConnectionFactory、PoolableConnectionFactory和PoolingDataSource类。 图1 dbcp几个重要实现类的关系 我们直接使用的最...

trayvon
2016/06/13
81
0
java jdbc连接数据库的

1、封装数据库连接 2、创建dao层接口 3、实现dao层接口 4、使用事务 (1)当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL...

红焖鲤鱼
2016/12/07
35
0
c3p0、dbcp、tomcat jdbc pool 连接池区别

查看资料,得知dbcp和c3p0都是单线程的,在高并发的环境下性能会非常低下, 决定换用tomcat自带的jdbc-pool,关于jdbc-pool的项目介绍。 区别参考链接:http://www.open-open.com/lib/view/o...

张嘴吃药
2017/10/30
0
0
在Java中开源的数据库连接池

在Java中开源的数据库连接池有以下几种 : 1, C3P0 C3P0是一个开放源代码的JDBC连接池,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。 https://gith...

壶漏子
2015/09/28
201
0
JDBC连接池的testQuery/validationQuery设置

在《Tomcat中使用Connector/J连接MySQL的超时问题》帖子中想要增加对连接池中连接的测试/验证,防止数据库认为连接已死而Web应用服务器认为连接还有效的问题,Mysql文档中提到Tomcat文档中的...

JAVA_NINA
2016/05/12
134
0

没有更多内容

加载失败,请刷新页面

加载更多

GlusterFS的再次节点重置和恢复

采用Ubuntu+ZFS+GlusterFS的存储集群,其中一个节点再次出现故障,gluster volume status显示为N/A状态。 检查网络,发现原来的IP地址 10.1.1.193发生了改变(估计被DHCP重新分派地址了),导...

openthings
29分钟前
5
0
BOM与正则表达式

BOM BOM的全称叫做Browser OjbectModel 浏览器对象模型,它定义了操作浏览器的接口。 BOM对象包括:Window、History、Navigator、Screen和Location。但是由于浏览器厂商的不同,BOM对象的兼容...

Panda-Q
29分钟前
1
0
牵头函数

箭头函数表达式的语法比函数表达式更短,并且没有自己的this,arguments,super或new.target。这些函数表达式更适用于那些本 来需要匿名函数的地方,并且它们不能用作构造函数。 首先:我们先...

wshining
34分钟前
1
0
mysql把一个数据库中的数据复制到另一个数据库中的表 2个表结构相同

首页 问题 全部问题 经济金融 企业管理 法律法规 社会民生 科学教育 健康生活 体育运动 文化艺术 电子数码 电脑网络 娱乐休闲 行政地区 心理分析 医疗卫生 精选 知道专栏 知道日报 知道大数据...

linjin200
35分钟前
1
0
python redis操作

redis命令:http://blog.csdn.net/yhl27/article/details/9936189 python redis: 干货 http://www.cnblogs.com/wangtp/p/5636872.html http://doc.redisfans.com/ http://developer.51cto.......

stys35
36分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部