線程池是什么?線程池與連接池有什么區別?線程池工作原理是什么?
你好,我是碼哥,可以叫我靚仔。
線程池是一種用于管理和重用線程的機制,它允許開發人員有效地執行并發任務。通過使用線程池,可以帶來了許多好處:
- 資源管理: 線程池能夠有效地管理系統資源,通過限制并發任務的數量和重用線程,減少了線程創建和銷毀的開銷,提高了系統資源利用率。
- 性能提升: 通過合理地配置線程池大小和任務隊列,可以優化任務執行流程,降低了線程的上下文切換成本,提高了任務的執行效率和系統的吞吐量。
- 避免資源耗盡: 線程池可以控制并發任務的數量,防止系統因創建過多線程而導致資源耗盡,從而提高了系統的穩定性和可靠性。
- 任務排隊: 線程池通過任務隊列來暫存尚未執行的任務,保證了任務的順序執行,并且能夠靈活地處理突發任務量,避免了系統的過載。
- 簡化并發編程: 使用線程池可以簡化并發編程的復雜性,開發人員無需手動管理線程的生命周期和任務的調度,只需將任務提交給線程池即可,從而降低了編程的復雜度和出錯的可能性。
接下來以 Java 中的線程池實現機制為例,帶你掌握線程池的工作機制。
線程池的工作機制
線程池的工作機制可以看作是一種生產者-消費者模型的應用。
在這個模型中,任務(生產者)被提交到線程池,然后線程池中的線程(消費者)從任務隊列中取出任務并執行,線程池模型架構如下圖:
- 開發人員使用 ThreadPoolExecutor 的 submit() 方法提交任務。
- 檢測線程池運行狀態,如果不是 RUNNING,則直接拒絕,線程池要保證在 RUNNING 的狀態下執行任務
- 提交的任務(通常實現了 Callable 或 Runnable 接口)會被封裝成一個 FutureTask 對象,該對象實現了 Future 接口,允許獲取任務執行的結果。
- 如果線程池中的核心線程數小于核心線程池大小(corePoolSize),則嘗試創建新的核心線程來執行任務。
- 如果當前核心線程數已經達到 corePoolSize,則將任務放入任務隊列中,等待工作線程獲取任務執行。
- 如果隊列已滿,而且當前線程池中的線程數量小于最大線程池大小(maximumPoolSize),則嘗試創建新的非核心線程來執行任務。
- 如果當前線程池中的線程數量已經達到最大線程池大小,則根據拒絕策略進行處理。
- 任務執行完成后,線程池將返回一個 Future 對象,通過這個對象可以獲取任務執行的結果。
線程池的執行流程圖如下所示。
線程池的狀態
Java 中的線程池具有不同的狀態,這些狀態反映了線程池在其生命周期中的不同階段和行為。主要的線程池狀態有以下幾種:
狀態 | 描述 |
RUNNING(運行中) | 表示線程池正在正常運行,并且可以接受新的任務提交。在這種狀態下,線程池可以執行任務,并且可以創建新的線程來處理任務。 |
SHUTDOWN(關閉中) | 表示線程池正在關閉中。在這種狀態下,線程池不再接受新的任務提交,但會繼續執行已提交的任務,直到所有任務執行完成。 |
STOP(停止) | 表示線程池已經停止,不再接受新的任務提交,并且嘗試中斷正在執行的任務。 |
TERMINATED(終止) | 表示線程池已經終止,不再接受新的任務提交,并且所有任務已經執行完成。在這種狀態下,線程池中的所有線程都已經被銷毀。 |
這些狀態是通過 ThreadPoolExecutor 類中的 ctl(control)字段來維護的,ctl 是一個 AtomicInteger 類型的變量,它的高 3 位表示線程池的運行狀態,低 29 位表示線程池中的工作線程數量。
在 ThreadPoolExecutor 中,通過位運算來修改和檢查 ctl 的值,以實現線程池狀態的轉換和管理。
通過 ctl 字段,ThreadPoolExecutor 類能夠高效地維護線程池的狀態和線程數量信息,從而實現了對線程池的有效管理和控制。
要注意的是,線程池的狀態不是直接設置的,而是通過調用 shutdown()、shutdownNow() 等方法觸發狀態的轉換。
例如,調用 shutdown() 方法會將線程池的狀態從 RUNNING 轉換為 SHUTDOWN。
拒絕策略
線程池的拒絕策略用于定義當線程池已滿并且無法處理新提交的任務時應該采取的行動。以下是 Java 中常見的線程池拒絕策略:
策略名稱 | 描述 |
AbortPolicy(默認策略) | 如果線程池已滿并且無法接受新任務,則會拋出 RejectedExecutionException 異常。這是默認的拒絕策略。 |
CallerRunsPolicy | 當線程池已滿時,會使用提交任務的線程來執行該任務。換句話說,如果無法接受新任務,則會由提交任務的線程自己執行該任務。 |
DiscardPolicy | 當線程池已滿時,會丟棄掉無法處理的新任務,而不會拋出異常。 |
DiscardOldestPolicy | 當線程池已滿時,會丟棄隊列中等待時間最長的任務,然后嘗試將新任務加入隊列。 |
除了上述標準拒絕策略之外,您還可以實現 RejectedExecutionHandler 接口來定義自定義的拒絕策略。這使您能夠根據應用程序的需求實現更復雜的拒絕邏輯。RejectedExecutionHandler 接口:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
提交任務給線程池觸發線程池的拒絕策略如下圖所示。
線程池使用場景
Java 線程池在業務中有許多實踐應用,以下是其中一些常見的實踐方式:
- Web 服務器:用 Tomcat 作為示例。Tomcat 是一個常見的 Java Web 服務器,它使用線程池來處理傳入的 HTTP 請求。每當有一個新的 HTTP 請求到達 Tomcat 服務器時,Tomcat 會從預先配置的線程池中獲取一個線程來處理該請求。這樣可以有效地管理并發請求,提高服務器的響應速度和穩定性。
- 并發任務處理:許多業務場景需要處理大量的并發任務,例如數據處理、文件上傳下載、消息處理等。線程池可以用于并發處理這些任務,提高任務的執行效率和系統的吞吐量。
- 異步處理:在某些業務場景中,需要執行一些耗時的操作,但不想讓主線程阻塞。線程池可以用于異步執行這些操作,例如發送郵件、短信通知、數據分析等。通過將任務提交給線程池,主線程可以立即返回,而任務會在后臺線程中異步執行。
線程池和連接池的區別
連接池是一組預先初始化和可重復使用的數據庫連接。它用于管理到數據庫的連接池,允許多個客戶端共享和重復使用數據庫連接。
連接池有助于通過減少建立和關閉數據庫連接的開銷來提高數據庫密集型應用程序的性能和可伸縮性。
線程池和連接池都是用于提高系統性能和資源利用率的重要技術,但它們的主要區別在于應用場景和管理的資源類型。
線程池用于管理可重復使用的線程資源,以便有效地執行并發任務,而連接池則用于管理可重復使用的數據庫連接資源,以便高效地處理數據庫訪問。
如下圖是數據庫連接池工作機制。