Tomcat JDBC Pool 源碼實(shí)現(xiàn)簡(jiǎn)單分析
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
轉(zhuǎn)自:sp42a
鏈接:https://zhangxin.blog.csdn.net/article/details/124901850
什么是連接池?
池,不由自主的會(huì)想到水池。小時(shí)候,我們都要去遠(yuǎn)處的水井挑水,倒進(jìn)家中的水池里面。這樣,每次要用水時(shí),直接從水池中「取」就行了。不用大老遠(yuǎn)跑去水井打水。數(shù)據(jù)庫連接池就如此,我們預(yù)先準(zhǔn)備好一些連接,放到池中。當(dāng)需要時(shí),就直接獲取。而不要每次跟數(shù)據(jù)庫建立一個(gè)新的連接。特別對(duì)數(shù)據(jù)庫連接這類耗時(shí),耗資源的操作。當(dāng)連接用完后,再放回池中,供后續(xù)使用。
連接池的作用?
避免多次去創(chuàng)建資源。例如,創(chuàng)建新的數(shù)據(jù)庫連接,500ms 輕輕松松就消耗了。建立 TCP 連接,數(shù)據(jù)庫賬號(hào)驗(yàn)證等等。這性能消耗起來,可是非常大的。在稍大的系統(tǒng)內(nèi),連接池是必備的。同時(shí),對(duì)技術(shù)人員要求,對(duì)連接池的掌握也是必須的。
tomcat-jdbc-pool 的特色
基于 jdk1.5 后的并發(fā)實(shí)現(xiàn)。代碼簡(jiǎn)潔,精練。核心的類就2,3個(gè)。對(duì)池的控制就在 org.apache.tomcat.jdbc.pool.ConnectionPool 中搞定。
先前有簡(jiǎn)單看過 dbcp1.x, c3p0 等等,代碼量真不少,邏輯復(fù)雜。想熟悉池的設(shè)計(jì),可以仔細(xì)讀讀 tomcat-jdbc-pool,非??焖俚娜胧?。在 dbcp2 的實(shí)現(xiàn)時(shí),跟 tomcat-jdbc-pool 思路一致(完全 copy 的版本)。對(duì)于連接池來說,最基本的特點(diǎn)就是:
有一定的容量,及已經(jīng)創(chuàng)建好的對(duì)象
有「借」有「還」操作的接口
池中「借出」連接是怎么個(gè)過程?
在 jdbc-pool 設(shè)計(jì)有 2 隊(duì)列,分別為 busy 和 idle,存儲(chǔ)「正在使用」和「空閑」的連接。都采用 ArrayBlockingQueue 以保證線程安全。
當(dāng)有請(qǐng)求「借」的動(dòng)作過來時(shí),從 idle 中 poll 一個(gè)連接,然后將該連接再 offer 至 busy 隊(duì)列中。這是最基本最純凈的思路。
當(dāng) idle 連接不夠時(shí),內(nèi)部會(huì)再去創(chuàng)建新的連接返回給客戶端。
但是,做為「池」必須的職責(zé)之一是控制總量,不會(huì)任你去增長。那么,有意思來了,他是怎么控制總量的咧?我們可以通俗點(diǎn)稱『占坑法』(tomcat 中也有不少場(chǎng)景采用這方式)。
首先池中有維護(hù)連接數(shù)總量「計(jì)數(shù)器」(采用 AtomicInteger 保證線程安全,每次新增或銷毀都會(huì)變更)?!赫伎臃ā痪驮诿看我聞?chuàng)建連接池,先總量計(jì)數(shù)器+1(占位),再比較是否達(dá)到配置的池的最大連接數(shù)。如果沒有達(dá)到,則創(chuàng)建新的;如果已達(dá)到了,則等待現(xiàn)有連接釋放,再取走。有點(diǎn)類似,大學(xué)時(shí)先用本書去搶位置占著。大致實(shí)現(xiàn)代碼【已經(jīng)對(duì)源碼簡(jiǎn)化】如下:
public class ConnectionPool {
// 連接數(shù)的總量
private AtomicInteger size = new AtomicInteger(0);
// 所有正在使用中的連接
private BlockingQueue<PooledConnection> busy;
// 所有空閑的連接
private BlockingQueue<PooledConnection> idle;
PooledConnection con = idle.poll();//
while (true) {
if (con != null) {// 如果從空閑連接隊(duì)列中取出的連接不為空
// 把這個(gè)連接加入正在使用中的連接列表,***并返回
busy.offer(con);// 這簡(jiǎn)化了,在源碼中這兒會(huì)對(duì)連接進(jìn)行校驗(yàn)、檢查或進(jìn)行連接。
return con;
}
// 下面判斷空閑連接隊(duì)列中取出的連接為空的情況
// 沒有超出連接總數(shù)
if (size.get() < getPoolProperties().getMaxActive()) {
// 占坑神技
if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {// 超出連接總數(shù)
// 既然沒了,那數(shù)量也減回去
// 再去等待其他連接歸還回來
size.decrementAndGet();
} else {
//新建一個(gè)物理連接
return createConnection(now, con, username, password);
}
}
// 等待一定時(shí)間 timetowait 后,再次從空閑連接隊(duì)列中取出的連接,循環(huán)執(zhí)行上面過程
con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
}
}}用完后「歸還」連接是怎么個(gè)過程?
大致思路跟「借」操作相反落。當(dāng)然是無視那些「善后」的工作,只關(guān)注資源的管理。但是,做為連接池必須的職責(zé)之一,并不真實(shí)的斷開與數(shù)據(jù)庫的連接。而只是放至 idle 隊(duì)列中,供客戶端下次再使用。如果有需要或必要肯定會(huì)釋放,技巧所在。大致代碼如下:
protected void returnConnection(PooledConnection con) {
if (con != null) {
try {
con.lock();
if (busy.remove(con)) {
// 跟允許的最大空閑數(shù)比較
if(idle.size() < poolProperties.getMaxIdle()) {
idle.offer(con);
// 源碼中調(diào)用 release
// 會(huì)根據(jù)配置項(xiàng)執(zhí)行一些校驗(yàn),例如:testOnReturn 為 true,則在回收時(shí)檢查連接是否正常
//release(con);
}
} catch(Exception e) {
//....
} finally {
con.unlock();
}
} //end if}
當(dāng)長時(shí)間運(yùn)行后,怎么回收無效的連接?
這是連接池必備的功能之一,類似檢查死鏈或者釋放自身過多的資源。比如,在高并發(fā)過后,對(duì)資源消耗量少時(shí),就釋放些不再使用的數(shù)據(jù)庫連接(真實(shí)斷開),維護(hù)合理的空格數(shù)量?!?** 連接池的minIdle定義了這個(gè)數(shù)量】
看到這應(yīng)用場(chǎng)景就自然想到,通過后臺(tái)線程定時(shí)掃描。——對(duì)的,就是這樣子。」——同樣在 ConnectionPool 這個(gè)類文件中的 PoolCleaner 類。寫在同個(gè)類文件中,便于用 this 進(jìn)行傳遞數(shù)據(jù)。不用再去構(gòu)造個(gè)復(fù)雜的 ConnectionPool 對(duì)象。
直接上代碼,「好代碼」就是最好的描述。
public class ConnectionPool {
/**
* Initialize the connection pool - called from the constructor
*/
protected void init(PoolConfiguration properties) throws SQLException {
initializePoolCleaner(properties);
}
public void initializePoolCleaner(PoolConfiguration properties) {
if (properties.isPoolSweeperEnabled()) {
poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis());
poolCleaner.start(); //只注冊(cè)一個(gè)清理器,并未啟動(dòng)線程。
} //end if
}
/**
* 檢查所有的空閑連接
*/
public void checkIdle(boolean ignoreMinSize) {
try {
if (idle.size()==0) return;
Iterator<PooledConnection> unlocked = idle.iterator();
//【重點(diǎn):如果空閑數(shù)量大于屬性MinIdle,則執(zhí)行清理!】
while ( (idle.size()>=getPoolProperties().getMinIdle())) &&unlocked.hasNext()) {
PooledConnection con = unlocked.next();
try {
con.lock();
//如果這時(shí)已到busy中,則不檢查了
if (busy.contains(con)) {
continue;
}
release(con);//釋放了物理連接
idle.remove(con);//從空閑隊(duì)列中移除
} finally {
con.unlock();
}
} //while
} catch (Exception e) {
//....
}
}
private static volatile Timer poolCleanTimer = null;
private static HashSet<PoolCleaner> cleaners = new HashSet<>();
//注冊(cè)一個(gè)清理器
private static synchronized void registerCleaner(PoolCleaner cleaner) {
unregisterCleaner(cleaner);
cleaners.add(cleaner);
//一堆構(gòu)造方式。。。
if (poolCleanTimer == null) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(ConnectionPool.class.getClassLoader());
poolCleanTimer = new Timer("PoolCleaner["+ System.identityHashCode(ConnectionPool.class.getClassLoader()) + ":"+
System.currentTimeMillis() + "]", true);
}finally {
Thread.currentThread().setContextClassLoader(loader);
}
}
//構(gòu)造定時(shí)掃描器
//java有內(nèi)庫非常強(qiáng)大,想用啥有啥呀
poolCleanTimer.scheduleAtFixedRate(cleaner, cleaner.sleepTime,cleaner.sleepTime);
}
//真實(shí)的處理線程在這兒。。。
protected static class PoolCleaner extends TimerTask {
protected WeakReference<ConnectionPool> pool;
PoolCleaner(ConnectionPool pool, long sleepTime) {
//弱引用,不了解的可以google下
this.pool = new WeakReference<>(pool);
}
@Override
public void run() {
ConnectionPool pool = this.pool.get();
if (pool == null) {
stopRunning();
} else if (!pool.isClosed()) {
if (pool.getPoolProperties().getMinIdle() < pool.idle.size()) {
pool.checkIdle(); //【重點(diǎn),如果空閑數(shù)量大于MinIdle,則執(zhí)行checkIdle】
}
}
}
public void start() {
registerCleaner(this); //并未啟動(dòng)線程,只是注冊(cè)一個(gè)清理器
}
public void stopRunning() {
unregisterCleaner(this);
}
}}????
往 期 推 薦
1、拖動(dòng)文件就能觸發(fā)7-Zip安全漏洞,波及所有版本
3、一次 SQL 查詢優(yōu)化原理分析:900W+ 數(shù)據(jù),從 17s 到 300ms
4、Redis數(shù)據(jù)結(jié)構(gòu)為什么既省內(nèi)存又高效?
點(diǎn)分享
點(diǎn)收藏
點(diǎn)點(diǎn)贊
點(diǎn)在看





