們經常會遇到這樣那樣的連接配接未關閉的問題,連接配接沒有及時關閉導緻的直接後果就是記憶體洩漏直至down機。我們也都知道解決的方式,但是在解決了問題之後經常會思考為什麼會這樣呢?連接配接close()掉,然後在建立不是很浪費cpu等系統資源嘛?有沒有更好的方法解決呢?大家也經常聽到連接配接池、線程池之類的線程、池的概念,那麼究竟這些概念與我們的連接配接有什麼關系呢?
下面我就想就上面的問題談談我的一點淺見,請大家批評指正。
大家都知道java語言是一種語言級的多線程機制的面向對象語言。比如說,java的基類object,它就有一些諸如wati(),notify(),notifyall()等線程控制的方法。如果大家學習過作業系統的化,我想應該知道線程存在同步和異步的問題,解決的方法很多,其中就有“信号量機制”實作線程的同步,java的這種同步機制與作業系統大同小異。同步機制是一個比較複雜的問題,如果感興趣可以找一本作業系統的書看看。
下面簡單介紹一些程序和線程的概念:
1. 程序:
程序是資源配置設定和獨立運作的基本機關。程序的定義很多,下面列舉一些
Ø 程序是程式的一次執行;
Ø 程序是可以和别的計算機并發執行的計算;
Ø 程序可定義為一個資料結構及能在其上進行操作的一個程式
Ø 程序是一個程式及其資料在處理機上順序執行時發生的活動;
Ø 程序時程式在一個資料集合上的運作過程,時系統進行資源配置設定和排程的一個獨立機關。
2. 線程
由于程序是一個資源擁有者,因而在程序的建立、撤消和切換過程中,系統必須為之付出較大的時空開銷。也正因為如此,在系統中所設定的程序數目不宜過多,程序切換的頻率也不宜過高,但這也就限制了并發程度的進一步提高。是以便引出了線程的概念
把線程作為排程和分派的基本機關,而把程序作為資源擁有的基本機關,使傳統程序的兩個屬性分開,線程便能輕裝運作,進而顯著提高系統的并發程度。
Ø 在同一個程序内可以有多個線程;
Ø 同一個程序内的線程切換不會引起程序切換;
Ø 一個程序的線程切換到另一個程序的線程時會引起程序切換
3. jsp/servlet
而我們常用的jsp/ervlet這種j2ee的體系結構正是建立在java的多線程機制之上的。jsp/servlet容器會自動使用線程池等技術來支援系統的運作。是以,jsp/servlet的實質是一種線程技術,jsp會在運作時被編譯成servlet來運作,如圖所示:
當用戶端向伺服器發出一個請求時,servlet容器會配置設定一個線程專門處理這個請求,線程内容就是jsp/servlet應用程式。
這部分内容與本主體無關,隻是順便說說。
4. 線程池
首先介紹一下線程池:
線程的建立和銷毀,以及切換,執行都是要耗費系統資源的。當系統通路量比較大的時候,伺服器内就會建立太多的線程,直至資源完全消耗,這對于應用系統的正常運作是有緻命傷害的。
為了能夠在通路尖峰時限制活動線程的數量,同時減少線程頻繁建立和銷毀帶來的系統開銷,提高系統的大通路量的處理性能和速度,需要事先建立一定數量的線程供調用者循環反複使用,這就是“池”技術。
線程的基本原理是基于隊列queue這種資料結構的,通過不斷查詢隊列queue是否有可以運作的線程。如果有,就立即運作線程,沒有,則鎖定等待,直到有新的線程加入被解鎖。(這種鎖定機制,就是所謂的“信号量機制”)。
一種線程池必須解決如下的問題:死鎖、資源不足、并發錯誤、線程洩漏和請求過載。下面我們具體舉一個成熟的開源線程池的例子來說明線程池的原理:
pooledexecutor pool=new pooledexecutor(new boundedbuffer(20),100);
pool.setminimumpoolsize(10);//最小線程數為10
poole.setkeepalivetime(-1);//線程一直運作
上面的語句設定了線程的最大數目為100,這樣,就可以保護系統不會因為通路量增加導緻線程數目的無限增加。使用該線程池如下:
pool.execute(java.lang.runnable 自己的線程);
這一句實際上是将“自己的線程”加入一個隊列中,而隊列(先進先出fifo)另一段正開啟多個線程不斷讀取這個隊列,一旦隊列中有空閑的線程,線程管理器就将讀取并配置設定線程來運作它。
public void execute(runnable command) throws interruptedexception {
for (;;) { //一直循環
synchronized (this) {
if (!shutdown_) { //確定線程池沒有關閉
int size = poolsize_; //目前線程池中線程的數目
if (size < minimumpoolsize_) { //如果目前線程數目少于線程池最小數目
addthread(command);
return;
}
//如果目前線程池中有超過或等于最小數目的線程
//配置設定一個存在的空閑線程來運作command,handoff是隊列
if (handoff_.offer(command, 0)) {
//如果不能配置設定已有的線程來運作command,那麼建立一個新線程
if (size < maximumpoolsize_) {
}
}
//如果阻塞,則請求幫助
if (getblockedexecutionhandler().bolckedaction(command)) {
return;
}
}
由上面的代碼可見,pooledexecutor線程池的原理是,當執行execute加載一個應用系統的線程時,線程池内部首先檢查目前線程數目是否達到設定的最小數目。如果沒有達到,啟動新線程運作;如果達到了,那麼檢查有無空閑線程可用;如果沒有空閑的,則建立新線程,直到達到最大數目。
使用線程池的好處是:首先是循環使用,一經建立後,空閑的線程可以被反複使用,提高了運作效率;其次有最大數目的限制,保證了系統的安全性。
5. 連接配接池
終于輪到連接配接池了,通過上面的介紹,我們對線程及線程池都有個一個大緻的了解。
在正常情況下,直接使用jdbc調用資料庫可以滿足一個小型系統的要求,但是當系統規模比較大的情況下,就會出現資料庫的通路量迅速提升而令伺服器不堪重負的現象,因而為了解決這一性能問題,常常會使用資料庫連接配接池作為一個緩存的方式解決。
連接配接池類似上面介紹的線程池。
每次資料庫連接配接的建立都需要花費一定的時空費用,而使用連接配接池,可以事先建立連接配接。當應用程式需要開始使用時,就從連接配接池中擷取一個連接配接使用,應用程式使用完畢,通過close()方法将連接配接歸還連接配接池。講到這裡,我門就不必在擔心close()方法會不會影響性能了,完全可以放心大膽的使用。因為,它實際上并沒有關閉連接配接,而是将連接配接歸還連接配接池,供下次使用。
當并發增加是,連接配接池會不斷的自動建立新的連接配接滿足調用,直到達到連接配接池的最大數目;當連接配接池連接配接減少甚至沒有時,連接配接池自動關閉一些連接配接,保持最小數目。
是以連接配接池的使用節省了連接配接建立時間,消除了資料庫頻繁連接配接帶來的開銷和瓶頸。
小提示:不知道大家有沒有注意到配置websphere時有關于連接配接池最大最小數目的配置。呵呵,道理就在這。
那麼,我們經常面對的連接配接未關閉的問題導緻的系統速度很慢的問題就很容易說明了,就是因為線程池已經達到了最大數目,沒有可用的了。是以,其他操作隻有等待的份,等待那些應用用完了,被垃圾回收了,才能釋放出可用的連接配接。