慎用Java 自帶兩種線程池!

Java5相比以前的Java版本,在併發編程上,有了很是大的提升,加了不少類,提供了不少可用於併發編程的工具包和工具類。尤爲爲人們所稱道的,就是Java自帶的線程池。 html

 

   Java5線程池的介紹文章,能夠說在網上比比皆是,我就再也不重複了,只是簡單提一下,線程池給併發程序帶 來了幾個好處: java

 

    一、建立和銷燬線程的開銷 編程

   二、保護系統資源,避免建立太多的線程致使系統崩潰 api

   三、簡化編程模型 緩存

 

Java5自帶的線程池( ThreadPool),用於併發系統的,主要有: 服務器

緩存線程池(newCachedThreadPool):每一個任務過來後都會建立一個線程,任務結束後,線程緩存一段時間,下次任務過來後,若是有以前緩存的線程就無須再建立而是直接使用。 併發

 

固定數量線程池(newFixedThreadPool):建立固定線程數量的線程池,若是任務數大於線程池中線程的數量,那麼任務將等待。 oracle

 

其實,咱們看Java的源代碼,就會發現,上面兩種線程池,都是調用Java的ThreadPoolExecutor來實現的。其構造函數以下: 函數

 

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

 

咱們能夠看到,其實是須要制定核心線程數、最大線程數、存活時間和時間單位、選擇的隊列這些參數。 工具

其中,核心線程數的概念相當重要,當有任務到來時,若是線程池中的線程數還不夠核心線程數,則啓動一個新的線程來運行任務(即使其它線程在空閒);若是線程池中的線程已經達到核心數,但未達到最大數,則任務放到隊列中等待空閒的核心線程處理;除非隊列滿,不會啓動新的線程。

 

從這裏,咱們再來看緩存線程池和固定數量線程池,很是簡單可以看出,緩存線程池的核心數爲1,最大數爲1,隊列長度是1;固定線程池核心數和最大數都是設定的數量。

 

由於Java提供了這兩種線程池,因此通常狀況下,咱們可能都以爲不須要再寫別的線程池實現了,直接用這兩種吧。結果在產品開發的過程當中,筆者就遇到了很大的一個坑,差點掉進去出不來。

 

當時是要開發一款高性能、高穩定性的C/S模式的服務器產品,Java5以後,使用線程池改進了任務派發的部分,當時覺得這兩種線程池都應該可以知足要求,就先採用固定數量線程池來試,結果發現若是服務器和客戶端都採用長鏈接,固定數量線程池存在很大的設計上的bug。

 

例如任務線程池核心數100,最大數100,客戶端也正好100個鏈接連上了服務器。由於長鏈接機制下,爲了保證處理效率,流程是服務器端ServerSocket在accept以後,不停循環,接受到請求後處理,而後繼續等待鏈接上的數據,直至接收到客戶端斷開的指令或服務器端超時。

 

以下圖:



 

 

能夠想象,若是這時候第101個鏈接連上了服務器,再沒有新的鏈接,TCP鏈接正常,任務也被接受放到了隊列裏,那麼這個任務就只能乖乖等在隊列裏等着超時,別的什麼都作不了!除非改變上面流程圖中的方式,線程再也不這樣循環,而是直接返回,這個鏈接上新的數據過來由另外的線程處理,等待線程池從新分配,但這樣效率必定不如圖中的方式好,不然沒有什麼好的辦法。

 

對於一些使用場景複雜的服務器端,客戶端長鏈接和短鏈接均可能有的這種場景,使用固定大小線程池,就必須考慮這個問題。或者犧牲效率,或者深刻考慮長連線程帶來的問題。

 

既然固定大小有問題,那就看看緩存線程池吧,麻煩更大了,若是服務器沒有別的輔助控制,一會兒涌入大量的客戶鏈接,服務器一會兒須要啓動大量的線程,頗有可能崩潰,這個也是沒法接受的。

 

    從這裏能夠看到,Java自帶的兩種線程池,實際上是各有各自的適用場景,對池中的任務,也都有本身的要求和限制,在適用這些基礎設施來設計系統以前,首先應該對這些進行透徹的分析,不要等到出現bug才醒悟。上面提到的bug,由於沒有清晰的錯誤信息,就很是不容易分析出來。

 

    若是你的服務器面臨這麼複雜多變的客戶端,並且既要求效率上不能犧牲,又但願使用Java的線程基礎設施,那麼必定要在這裏慎重再慎重。

相關文章
相關標籤/搜索