<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<Connection> 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")<0) {
throw x;
}
/we can only retrieve a future if the underlying queue supports it.
if (idle instanceof FairBlockingQueue<?>) {
Future<PooledConnection> pcf = ((FairBlockingQueue<PooledConnection>)idle).pollAsync();
return new ConnectionFuture(pcf);
} else if (idle instanceof MultiLockFairBlockingQueue<?>) {
Future<PooledConnection> pcf = ((MultiLockFairBlockingQueue<PooledConnection>)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() < getPoolProperties().getMaxActive()) {
//atomic duplicate check
if (size.addAndGet(1) > 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()<=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 && 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) >= 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() && !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()) && 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