天天看點

Tomcat JDBC pool源碼部析

<b>最近跟随</b><b>Tomcat7.0</b><b>開發了一個</b><b>JDBC </b><b>連接配接池。</b>

<b> Svn:</b> <b>http://svn.apache.org/repos/asf/tomcat/trunk/modules/jdbc-pool</b>

<b></b>

應用使用JDBC連接配接也,主要關注以下三個方面:

1.  擷取連接配接

2.  歸還連接配接

3.空閑連接配接關閉。

<b>一. </b><b>擷取連接配接</b><b></b>

<b>ConnectionPool</b><b>提供三個接口用于擷取連接配接:</b>

public Future&lt;Connection&gt; getConnectionAsync() throws SQLException { 

     try { 

         PooledConnection pc = borrowConnection(0, null, null); 

         if (pc!=null) { 

             return new ConnectionFuture(pc); 

         } 

     }catch (SQLException x) { 

         if (x.getMessage().indexOf("NoWait")&lt;0) { 

             throw x; 

     } 

/we can only retrieve a future if the underlying queue supports it. 

     if (idle instanceof FairBlockingQueue&lt;?&gt;) { 

         Future&lt;PooledConnection&gt; pcf = ((FairBlockingQueue&lt;PooledConnection&gt;)idle).pollAsync(); 

         return new ConnectionFuture(pcf); 

     } else if (idle instanceof MultiLockFairBlockingQueue&lt;?&gt;) { 

             Future&lt;PooledConnection&gt; pcf = ((MultiLockFairBlockingQueue&lt;PooledConnection&gt;)idle).pollAsync(); 

             return new ConnectionFuture(pcf); 

     } else { 

         throw new SQLException("Connection pool is misconfigured, doesn't support async retrieval. Set the 'fair' property to 'true'"); 

public Connection getConnection() throws SQLException { 

     //check out a connection 

     PooledConnection con = borrowConnection(-1,null,null); 

     return setupConnection(con); 

 } 

public Connection getConnection(String username, String password)    throws SQLException { 

     // check out a connection 

    PooledConnection con = borrowConnection(-1, username,      password); 

第一個方法:getConnectionAsync用于擷取一個連接配接的Feature.它用于支援以異步的方式擷取連接配接。後兩個方法不同之處就是傳遞了所需連接配接的使用者名與密碼。我們這裡得點分析第三個方法.

PooledConnection con = borrowConnection(-1, username, password);

borrowConnection方法從空閑隊列中擷取一個連接配接,或建立一個連接配接。看一下源碼:

/** 

 * Thread safe way to retrieve a connection from the pool 

  * @param wait - time to wait, overrides the maxWait from the properties, 

  * set to -1 if you wish to use maxWait, 0 if you wish no wait time. 

     * @return PooledConnection 

     * @throws SQLException 

     */ 

private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException { 

    //如果連接配接被關閉則直接抛出異常 

        if (isClosed()) { 

            throw new SQLException("Connection pool closed."); 

        } //end if 

        //get the current time stamp 

        long now = System.currentTimeMillis(); 

        //see if there is one available immediately 

       /*從空閑隊列中擷取一個連接配接。 其實idle裡存在連接配接對象有的可能并沒有 

綁定實體連接配接。這也是Tomcat jdbc pool的一個特别,連接配接在将要被使用時, 

才會初始化*/ 

PooledConnection con = idle.poll(); 

        while (true) { 

            if (con!=null) { 

                //configure the connection and return it 

                /*這裡又出現一個borrowConnection的重載方法。該方法對從空閑隊列中取到的連接配接對象進行配置和驗證,稍後評述*/ 

                PooledConnection result = borrowConnection(now, con, username, password); 

                //null should never be returned, but was in a previous impl. 

                // null should never be returned這句注釋不對,根據後面的代碼 

                // 來看,null是有可能發生。 

                if (result!=null) return result; 

            } 

            //if we get here, see if we need to create one 

            //this is not 100% accurate since it doesn't use a shared 

            //atomic variable - a connection can become idle while we are creating 

            //a new connection 

            /*從上面的英文注釋我們很明白,當執行到這裡時,唯一的可能是idle隊列沒能取到連接配接對象。

如果條件允許,我們将建立新的連接配接.在這裡作者用了一個特别的算法,也是tomcat代碼中常用的,

我們姑且稱他占位法(我一般這麼叫)。這個算法的特點就是先在計數器Size中占一個位置

(Size是原子變量。能夠解決并發問題)。即size+1.然後檢查size有沒有超标。如果超标

則減去剛才新加的1。否則建立一個新的連接配接。不過這裡我注意的是,如果建立新連接配接時失敗,

size也必須減1。其實與大學時的用書搶位子異曲同工。*/ 

            if (size.get() &lt; getPoolProperties().getMaxActive()) { 

                //atomic duplicate check 

                if (size.addAndGet(1) &gt; getPoolProperties().getMaxActive()) { 

                  //if we got here, two threads passed through the first if 

                    size.decrementAndGet(); 

                } else { 

                    //create a connection, we're below the limit 

                    //後面再描述這個方法。 

                    return createConnection(now, con, username, password); 

                } 

            } //end if 

        //到這裡則表示連接配接池已滿,不能建立新的連接配接,我們隻能等待其他線程釋放的連接配接 

            //calculate wait time for this iteration 

            long maxWait = wait; 

//if the passed in wait time is -1, 

//means we should use the pool property value 

            if (wait==-1) { 

                maxWait = (getPoolProperties().getMaxWait()&lt;=0)?Long.MAX_VALUE:getPoolProperties().getMaxWait(); 

           //我們需要計算本次最多能容忍的等待。為什麼要計算呢。因為我們可能中間被假//喚醒但卻沒能拿到連接配接。 

long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now)); 

            waitcount.incrementAndGet(); 

            try { 

                //retrieve an existing connection 

                con = idle.poll(timetowait, TimeUnit.MILLISECONDS); 

            } catch (InterruptedException ex) { 

                if (getPoolProperties().getPropagateInterruptState()) { 

                    Thread.currentThread().interrupt(); 

                    Thread.interrupted(); 

                SQLException sx = new SQLException("Pool wait interrupted."); 

                sx.initCause(ex); 

                throw sx; 

            } finally { 

                waitcount.decrementAndGet(); 

            //no wait, return one if we have one 

            if (maxWait==0 &amp;&amp; con == null) { 

                throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " + 

                "NoWait: Pool empty. Unable to fetch a connection, none available["+busy.size()+" in use]."); 

            //we didn't get a connection, let’s see if we timed out 

            if (con == null) { 

                … 

              if ((System.currentTimeMillis() - now) &gt;= maxWait) { 

                    throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " + 

                        "Timeout: Pool empty. Unable to fetch a connection in " + (maxWait / 1000) + 

                        " seconds, none available[size:"+size.get() +"; busy:"+busy.size()+"; idle:"+idle.size()+"; lastwait:"+timetowait+"]."); 

                    //no timeout, lets try again 

                    //如果沒有逾時,我們繼續去擷取連接配接。 

                    continue; 

        } //while 

    } 

waitTime表示連接配接請求者容忍等待的最大時間,逾時沒有擷取到連接配接則抛出PoolExhaustedException異常。OK。

下面我們看中間遇到的borrowConnection的重載方法:

protected PooledConnection borrowConnection(long now, PooledConnection con, String username, String password)

protected PooledConnection createConnection(long now, PooledConnection notUsed, String username, String password)

首先看第一個:

     * Validates and configures a previously idle connection 

     * @param now - timestamp 

     * @param con - the connection to validate and configure 

     * @return con 

     * @throws SQLException if a validation error happens 

 protected PooledConnection borrowConnection(long now, PooledConnection con, String username, String password) throws SQLException { 

        //we have a connection, lets set it up 

        //flag to see if we need to nullify 

        boolean setToNull = false; 

        try { 

            //為目前連接配接加鎖

            con.lock(); 

           //驗證目前連接配接用使用者名與密碼是否符合需求 

            boolean usercheck = con.checkUser(username, password); 

            if (con.isReleased()) { 

                return null; 

            //對于沒标記為丢棄的連接配接且沒有初始化的連接配接進行初始化。 

            if (!con.isDiscarded() &amp;&amp; !con.isInitialized()) { 

                //attempt to connect 

                try { 

                    con.connect(); 

                } catch (Exception x) { 

                    release(con); 

                    setToNull = true; 

                    if (x instanceof SQLException) { 

                        throw (SQLException)x; 

                    } else { 

                        SQLException ex  = new SQLException(x.getMessage()); 

                        ex.initCause(x); 

                        throw ex; 

                    } 

            if (usercheck) { 

                if ((!con.isDiscarded()) &amp;&amp; con.validate(PooledConnection.VALIDATE_BORROW)) { 

                    //set the timestamp 

                    con.setTimestamp(now); 

                    //這裡添加LogAbandoned的功能是為了在檢測到連接配接洩露時, 

//擷取占用該連接配接的線程棧 

                    if (getPoolProperties().isLogAbandoned()) { 

                        //set the stack trace for this pool 

                        con.setStackTrace(getThreadDump()); 

               //放入busy隊列。如果不成功,則該連接配接将無法被追蹤。(這種情況不會出現) 

                    if (!busy.offer(con)) { 

                        log.debug("Connection doesn't fit into busy array, connection will not be traceable."); 

                    return con; 

            //if we reached here, that means the connection 

            //is either has another principal, is discarded or validation failed. 

            //we will make one more attempt 

            //in order to guarantee that the thread that just acquired 

            //the connection shouldn't have to poll again. 

            //這裡英語描述的很清楚了。如果連接配接的使用者名不符,被丢棄或驗證失敗, 

            //我們可以重連該連接配接,以滿足需求,而不是再去擷取其他的。 

                con.reconnect(); 

                if (con.validate(PooledConnection.VALIDATE_INIT)) { 

                    //validation failed. 

                    throw new SQLException("Failed to validate a newly established connection."); 

            } catch (Exception x) { 

                release(con); 

                setToNull = true; 

                if (x instanceof SQLException) { 

                    throw (SQLException)x; 

                    SQLException ex  = new SQLException(x.getMessage()); 

                    ex.initCause(x); 

                    throw ex; 

        } finally { 

            con.unlock(); 

            if (setToNull) { 

                con = null; 

        } 

(待續)

本文轉自 anranran 51CTO部落格,原文連結:http://blog.51cto.com/guojuanjun/1172327