文档章节

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

trayvon
 trayvon
发布于 2016/06/12 20:26
字数 1734
阅读 329
收藏 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
粉丝 15
博文 124
码字总数 184644
作品 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
在Java中开源的数据库连接池

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

壶漏子
2015/09/28
201
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中开源的数据库连接池有以下几种 : 1, C3P0 C3P0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate[1]一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement...

一一叶
2014/03/11
0
1

没有更多内容

加载失败,请刷新页面

加载更多

ConcurrentHashMap 高并发性的实现机制

ConcurrentHashMap 的结构分析 为了更好的理解 ConcurrentHashMap 高并发的具体实现,让我们先探索它的结构模型。 ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEnt...

TonyStarkSir
今天
3
0
大数据教程(7.4)HDFS的java客户端API(流处理方式)

博主上一篇博客分享了namenode和datanode的工作原理,本章节将继前面的HDFS的java客户端简单API后深度讲述HDFS流处理API。 场景:博主前面的文章介绍过HDFS上存的大文件会成不同的块存储在不...

em_aaron
昨天
2
0
聊聊storm的window trigger

序 本文主要研究一下storm的window trigger WindowTridentProcessor.prepare storm-core-1.2.2-sources.jar!/org/apache/storm/trident/windowing/WindowTridentProcessor.java public v......

go4it
昨天
6
0
CentOS 生产环境配置

初始配置 对于一般配置来说,不需要安装 epel-release 仓库,本文主要在于希望跟随 RHEL 的配置流程,紧跟红帽公司对于服务器的配置说明。 # yum update 安装 centos-release-scl # yum ins...

clin003
昨天
9
0
GPON网络故障处理手册

导读 为了方便广大网络工作者工作需要,特搜集以下GPON网络处理流程供大家学习参考。开始—初步定为故障—检查光纤状况—检查ONU状态--检查设备运行状态—检查设备数据配置—检查上层设备状态...

问题终结者
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部