線程池原理、定義、優勢

線程池原理

在 Android 中,由於主線程的諸多限制,像網絡請求等一些耗時的操作我們必須在子線程中運行。我們往往會通過 new Thread 來開啓一個子線程,待子線程操作完成以後通過 Handler 切換到主線程中運行。這麼以來我們無法管理我們所創建的子線程,並且無限制的創建子線程,它們相互之間競爭,很有可能由於佔用過多資源而導致死機或者內存不足。所以在 Java 中爲我們提供了線程池來管理我們所創建的線程。

線程池的優勢

①降低系統資源消耗,通過重用已存在的線程,降低線程創建和銷燬造成的消耗;

②提高系統響應速度,當有任務到達時,無需等待新線程的創建便能立即執行;

③方便線程併發數的管控,線程若是無限制的創建,不僅會額外消耗大量系統資源,更是佔用過多資源而阻塞系統或內存不足等狀況,從而降低系統的穩定性。線程池能有效管控線程,統一分配、調優,提供資源使用率;

④更強大的功能,線程池提供了定時、定期以及可控線程數等功能的線程池,使用方便簡單

線程池執行流程

①如果在線程池中的線程數量沒有達到核心的線程數量,這時候會啓動一個核心線程來執行任務。如果線程池中的線程數量已經超過核心線程數,這時候任務就會被插入到任務隊列中排隊等待執行。

②由於任務隊列已滿,無法將任務插入到任務隊列中。這個時候如果線程池中的線程數量沒有達到線程池所設定的最大值,那麼這時候就會立即啓動一個非核心線程來執行任務。如果線程池中的數量達到了所規定的最大值,那麼就會拒絕執行此任務,這時候就會調用 RejectedExecutionHandler 中的 rejectedExecution 方法來通知調用者。

創建線程池的方法

(1)ThreadPoolExecutor

我們可以通過 ThreadPoolExecutor 來創建一個線程池。

(2)可以通過 Executors 的靜態工廠方法創建線程池:

① newFixedThreadPool,固定大小的線程池,核心線程數也是最大線程數,不存在空閒線程,keepAliveTime = 0。該線程池使用的工作隊列是無界阻塞隊列 LinkedBlockingQueue,適用於負載較重的服務器。

② newSingleThreadExecutor,使用單線程,相當於單線程串行執行所有任務,適用於需要保證順序執行任務的場景。

③ newCachedThreadPool,maximumPoolSize 設置爲 Integer 最大值,是高度可伸縮的線程池。該線程池使用的工作隊列是沒有容量的 SynchronousQueue,如果主線程提交任務的速度高於線程處理的速度,線程池會不斷創建新線程,極端情況下會創建過多線程而耗盡 CPU 和內存資源。適用於執行很多短期異步任務的小程序或負載較輕的服務器。

④ newScheduledThreadPool:線程數最大爲 Integer 最大值,存在 OOM 風險。支持定期及週期性任務執行,適用需要多個後臺線程執行週期任務,同時需要限制線程數量的場景。相比 Timer 更安全,功能更強,與 newCachedThreadPool 的區別是不回收工作線程。

⑤ newWorkStealingPool:JDK8 引入,創建持有足夠線程的線程池支持給定的並行度,通過多個隊列減少競爭。

創建線程池的參數

① corePoolSize:常駐核心線程數,如果爲 0,當執行完任務沒有任何請求時會消耗線程池;如果大於 0,即使本地任務執行完,核心線程也不會被銷燬。該值設置過大會浪費資源,過小會導致線程的頻繁創建與銷燬。

② maximumPoolSize:線程池能夠容納同時執行的線程最大數,必須大於等於 1,如果與核心線程數設置相同代表固定大小線程池。

③ keepAliveTime:線程空閒時間,線程空閒時間達到該值後會被銷燬,直到只剩下 corePoolSize 個線程爲止,避免浪費內存資源。

④ unit:keepAliveTime 的時間單位。

⑤ workQueue:工作隊列,當線程請求數大於等於 corePoolSize 時線程會進入阻塞隊列。

⑥ threadFactory:線程工廠,用來生產一組相同任務的線程。可以給線程命名,有利於分析錯誤。

⑦ handler:拒絕策略,默認使用 AbortPolicy 丟棄任務並拋出異常,CallerRunsPolicy 表示重新嘗試提交該任務,DiscardOldestPolicy 表示拋棄隊列裏等待最久的任務並把當前任務加入隊列,DiscardPolicy 表示直接拋棄當前任務但不拋出異常。

關閉線程池的方法

可以調用 shutdown 或 shutdownNow 方法關閉線程池,原理是遍歷線程池中的工作線程,然後逐個調用線程的 interrupt 方法中斷線程,無法響應中斷的任務可能永遠無法終止。

區別是 shutdownNow 首先將線程池的狀態設爲 STOP,然後嘗試停止正在執行或暫停任務的線程,並返回等待執行任務的列表。而 shutdown 只是將線程池的狀態設爲 SHUTDOWN,然後中斷沒有正在執行任務的線程。通常調用 shutdown 來關閉線程池,如果任務不一定要執行完可調用 shutdownNow。

線程池的選擇策略

可以從以下角度分析:

①任務性質:CPU 密集型、IO 密集型和混合型。②任務優先級。

③任務執行時間。④任務依賴性:是否依賴其他資源,如數據庫連接。

性質不同的任務可用不同規模的線程池處理,CPU 密集型任務應配置儘可能小的線程,如配置 Ncpu+1 個線程的線程池。由於 IO 密集型任務線程並不是一直在執行任務,應配置儘可能多的線程,如 2*Ncpu。混合型的任務,如果可以拆分,將其拆分爲一個 CPU 密集型任務和一個 IO 密集型任務,只要兩個任務執行的時間相差不大那麼分解後的吞吐量將高於串行執行的吞吐量,如果相差太大則沒必要分解。優先級不同的任務可以使用優先級隊列 PriorityBlockingQueue 處理。執行時間不同的任務可以交給不同規模的線程池處理,或者使用優先級隊列讓執行時間短的任務先執行。依賴數據庫連接池的任務,由於線程提交 SQL 後需要等待數據庫返回的結果,等待的時間越長 CPU 空閒的時間就越長,因此線程數應該儘可能地設置大一些,提高 CPU 的利用率。建議使用有界隊列,能增加系統的穩定性和預警能力,可以根據需要設置的稍微大一些。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_x3eoFTL28YLbi9wIevaXw