數據庫連接池-連接的關閉內幕

我們經常會遇到這樣那樣的連接未關閉的問題,連接沒有及時關閉導致的直接後果就是內存泄漏直至down機。我們也都知道解決的方式,但是在解決了問題之後經常會思考爲什麼會這樣呢?連接close()掉,然後在創建不是很浪費cpu等系統資源嘛?有沒有更好的方法解決呢?大家也經常聽到連接池、線程池之類的線程、池的概念,那麼究竟這些概念與我們的連接有什麼關係呢?

       下面我就想就上面的問題談談我的一點淺見,請大家批評指正。

       大家都知道Java語言是一種語言級的多線程機制的面嚮對象語言。比如說,java的基類object,它就有一些諸如wati(),notify(),notifyall()等線程控制的方法。如果大家學習過操作系統的化,我想應該知道線程存在同步和異步的問題,解決的方法很多,其中就有“信號量機制”實現線程的同步,java的這種同步機制與操作系統大同小異。同步機制是一個比較複雜的問題,如果感興趣可以找一本操作系統的書看看。

       下面簡單介紹一些進程和線程的概念:

1.         進程:

       進程是資源分配和獨立運行的基本單位。進程的定義很多,下面列舉一些

a         進程是程序的一次執行;

b         進程是可以和別的計算機併發執行的計算;

c         進程可定義爲一個數據結構及能在其上進行操作的一個程序

d        進程是一個程序及其數據在處理機上順序執行時發生的活動;
e        進程時程序在一個數據集合上的運行過程,時系統進行資源分配和調度的一個獨立單位。

2.         線程

由於進程是一個資源擁有者,因而在進程的創建、撤消和切換過程中,系統必須爲之付出較大的時空開銷。也正因爲如此,在系統中所設置的進程數目不宜過多,進程切換的頻率也不宜過高,但這也就限制了併發程度的進一步提高。因此便引出了線程的概念

把線程作爲調度和分派的基本單位,而把進程作爲資源擁有的基本單位,使傳統進程的兩個屬性分開,線程便能輕裝運行,從而顯著提高系統的併發程度。

a         在同一個進程內可以有多個線程;

b         同一個進程內的線程切換不會引起進程切換;

c         一個進程的線程切換到另一個進程的線程時會引起進程切換

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)) {

                     return;

                  }

                  //如果不能分配已有的線程來運行command,那麼創建一個新線程

                  if (size < maximumPoolSize_) {

                     addThread(command);

                     return;

                  }

 

              }

           }

           //如果阻塞,則請求幫助

           if (getBlockedExecutionHandler().bolckedAction(command)) {

              return;

           }

       }

    }

       由上面的代碼可見,PooledExecutor線程池的原理是,當執行execute加載一個應用系統的線程時,線程池內部首先檢查當前線程數目是否達到設定的最小數目。如果沒有達到,啓動新線程運行;如果達到了,那麼檢查有無空閒線程可用;如果沒有空閒的,則創建新線程,直到達到最大數目。

       使用線程池的好處是:首先是循環使用,一經創建後,空閒的線程可以被反覆使用,提高了運行效率;其次有最大數目的限制,保證了系統的安全性。

5.         連接池

終於輪到連接池了,通過上面的介紹,我們對線程及線程池都有個一個大致的瞭解。

在正常情況下,直接使用JDBC調用數據庫可以滿足一個小型系統的要求,但是當系統規模比較大的情況下,就會出現數據庫的訪問量迅速提升而令服務器不堪重負的現象,因而爲了解決這一性能問題,常常會使用數據庫連接池作爲一個緩存的方式解決。

連接池類似上面介紹的線程池。

每次數據庫連接的建立都需要花費一定的時空費用,而使用連接池,可以事先建立連接。當應用程序需要開始使用時,就從連接池中獲取一個連接使用,應用程序使用完畢,通過close()方法將連接歸還連接池。講到這裏,我門就不必在擔心close()方法會不會影響性能了,完全可以放心大膽的使用。因爲,它實際上並沒有關閉連接,而是將連接歸還連接池,供下次使用。

當併發增加是,連接池會不斷的自動創建新的連接滿足調用,直到達到連接池的最大數目;當連接池連接減少甚至沒有時,連接池自動關閉一些連接,保持最小數目。

因此連接池的使用節省了連接建立時間,消除了數據庫頻繁連接帶來的開銷和瓶頸。

小提示:不知道大家有沒有注意到配置websphere時有關於連接池最大最小數目的配置。呵呵,道理就在這。

那麼,我們經常面對的連接未關閉的問題導致的系統速度很慢的問題就很容易說明了,就是因爲線程池已經達到了最大數目,沒有可用的了。所以,其他操作只有等待的份,等待那些應用用完了,被垃圾回收了,才能釋放出可用的連接。


相關網址:http://blog.csdn.net/sleepbird/article/details/1010408



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章