默認配置下,Tomcat 會為每個連接器創建一個綁定的線程池(最大線程數 200)。在大多數情況下你不需要改這個配置(除非增大最大線程數以滿足高負載需要)。但是 Tomcat 喜歡在每個工作者線程的 thread-local 上下文緩存一些諸如 PageContext 以及標簽緩存的對象。正因如此,就會有你期望 Tomcat 能夠將線程關掉以清理出來一些內存的情況。此外,每個連接器維護自己的線程池的話,根據服務器的承受能力來設置一個(線程數)最高值會變得更加困難。解決這些問題的答案就是使用一個共享執行器。
通過讓所有的連接器都使用同一個共享的執行器,你可以預先對的整個應用能夠承載的最高並發請求數進行相關配置。執行器也讓線程池具備了閑時收縮忙時擴展的功能。至少在理論上是這樣的...
org.apache.catalina.core.StandardThreadExecutor
Tomcat 默認所使用的標准、內置執行器就是 StandardThreadExecutor。配置文檔訪問:http://tomcat.apache.org/tomcat-6.0-doc/config/executor.html。這些配置選項里有個取名不當的參數 "maxIdleTime",以下是關於標准執行器和空閑線程的關閉你需要了解的一些事情。
標准執行器內在地使用了一個 java.util.concurrent.ThreadPoolExecutor。它通過具有一個變量大小的工作線程的線程池進行工作,一旦這些線程完成了一個任務,將會等待一個阻塞隊列,直到一個新的任務進來。或者直到它等待了一個設定的時間,這時將會 "超時",該線程將被關閉。這里邊的關鍵點是第一個完成了一個任務的線程會首先被分配新的任務,線程池遵守一個先進先出(FIFO)的模式。在我們檢查它將如何影響 Tomcat 執行器的時候我們需要時刻注意這一點。
maxIdleTime 實際上是 minIdleTime
由於 Java ThreadPoolExecutor 的 FIFO 行為,每個線程在可能被關閉之前會等待最少 "maxIdleTime" 時間來接受新的任務。此外,還是由於線程池的 FIFO 的行為,因為最先進入空閑狀態的線程會被優先分配新任務,所以在它被關閉之前它最少也要等待 maxIdleTime 沒有任何請求進入的時間。這個的影響是執行器實際上無法為線程池設置適合平均負載(並發請求)的大小,它會更加請求進入的速度來調配這個大小。這看起來好像沒啥差別,但是對於 web 服務器來講,影響可就大了。舉個例子,同一時間進來了 40 個請求。線程池將擴展到 40 以適應該負載。之后的一段期間,同一時間只進入了一個請求。比方說每個請求的執行結束需要 500 ms,這就意味着在接下來的這段時間內,需要 20 秒才能把整個線程池里的線程執行一遍(記住,FIFO)。除非你把你的 maxIdleTime 設置到 20 秒以內,否則線程池將會一直持有 40 個線程,即使你的並發量從未超過 1。然而你也並不想把你的 maxIdleTime 設置的太小 - 這將導致你面臨線程被關閉的太快的風險。
結論
為了匹配平均負載,而不是一個請求進入的比率的話,要得到一個可以預期的線程池行為,比較合適的是執行器應該基於一個后進先出(LIFO)的模式。如果線程池能夠將最小等待空閑的線程來優先分配進入的任務的話,服務器就能夠在較低負載階段更好地關閉線程(以一個更加可以預測的方式)。在上面那個再簡單不過的例子中,初始負載為 40 之后一段時間的負載維持在 1,一個 LIFO 的線程池就能夠在 maxIdleTime 階段之后將大小合理地調整到 1。當然,並非總是要求你使用這種策略,但是如果你的目標是把 Tomcat 所持有的資源最小化,很不幸的是標准的執行器可能就不是你所期望的那樣了。
原文鏈接:https://papweb.wordpress.com/2010/10/30/understanding-tomcat-executor-thread-pooling/。