記一次dbcp數據庫鏈接池問題分析

   最開始使用數據庫鏈接池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方法上。下面來仔細看一下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 方法的實現就是驗證鏈接可不可用,若是可用就把鏈接加入到空閒鏈接鏈表中。

相關文章
相關標籤/搜索