學好執行緒池,搞定高併發!(文末福利)

帶你聊技術發表於2023-03-31

來源:JAVA日知錄


在真實高併發場景下,一般不會直接使用 Thread 類建立執行緒,而是使用執行緒池來建立並管理執行緒。可以這麼說,學好執行緒池對於併發程式設計是非常重要的。

01

執行緒池簡介

執行緒池的建立和回收是一個非常消耗系統資源的過程,如果在系統中頻繁地建立和回收執行緒,會極大降低程式的執行效能。並且,短時間內建立大量的執行緒可能造成 CPU 佔用 100%、當機或記憶體溢位等問題。而使用執行緒池就能非常輕鬆地解決這些問題。

執行緒池核心類繼承關係

執行緒池是 Java 從 JDK 1.5 版本開始提供的一種執行緒使用模式,能夠自動建立和回收執行緒,並管理執行緒的生命週期。線上程池中能夠管理和維護多個執行緒。

Java 的執行緒池主要是透過 Executor 框架實現的,涉及 Executor 介面、ExecutorServcie 介面、AbstractExecutorService 抽象類、ScheduledExecutorService 介面、ThreadPoolExecutor 類和ScheduledThreadPoolExecutor 類。執行緒池核心類繼承關係如下圖所示。

學好執行緒池,搞定高併發!(文末福利)

實現執行緒池最核心的類是ThreadPoolExecutor,而 ScheduledThreadPoolExecutor 類實現了定時任務功能,能夠使提交到執行緒池中的任務定時、定期執行。為了便於建立執行緒池,除了上圖所示的介面和類,JDK 還提供了一個 Executors 工具類,Executors 類中封裝了建立執行緒池的各種方法,專門用於建立執行緒池。不過,在真實的高併發場景下,並不推薦使用 Executors 工具類建立執行緒池,而是推薦直接使用 ThreadPoolExecutor 類建立執行緒池。

02

執行緒池的優點

這裡,綜合對比直接使用 Thread 類建立執行緒的弊端與使用執行緒池的優點,來加深讀者對執行緒池的理解。

1.直接使用 Thread 類建立執行緒的缺點

直接在程式中使用 Thread 類建立執行緒的方式是非常不可取的,主要體現在如下幾方面。

(1)每次透過 Thread 類建立一個執行緒物件的效能是非常差的,每次建立 Thread 物件後,呼叫 Thread 的 start()方法都會在作業系統層面分配一個與之對應的執行緒,這個過程比較耗時。

(2)直接使用 Thread 類建立執行緒缺乏有效的統一管理機制,如果在短時間內建立大量執行緒,執行緒之間就會競爭系統資源,可能造成 CPU 佔用 100%、當機或者記憶體溢位等問題。

(3)直接使用 Thread 類建立執行緒提供的執行緒功能非常有限,例如,無法讓執行緒執行更多的任務、無法定期執行某些任務等。

(4)直接使用 Thread 類建立執行緒,無法對執行緒進行有效監控。

2.使用執行緒池管理執行緒的優點

使用執行緒池能夠非常容易地解決直接使用 Thread 建立執行緒產生的問題,主要體現在如下幾方面。

(1)執行緒池能夠複用執行緒資源,有效減少了執行緒的建立和回收頻率,減少了執行緒的建立與回收對系統效能造成的影響,比直接使用 Thread 類建立執行緒的系統效能高。

(2)使用執行緒池能夠有效控制最大併發執行緒數,提高系統資源的利用率。建立的執行緒數是可控的,短時間內不會因為建立大量的執行緒導致執行緒過多地競爭資源,引起執行緒阻塞。

(3)線上程池中可以定時或定期執行某個或某些任務,提供了單執行緒執行任務的機制,也能夠控制併發執行緒數。執行緒池提供了監控執行緒資源的方法,可以對執行緒池中的執行緒資源進行實時監控。

03

ThreadPoolExecutor 類

ThreadPoolExecutor 是執行緒池中最核心的類,透過檢視 ThreadPoolExecutor 的程式碼可以得知,在使用 ThreadPoolExecutor 類的構造方法建立執行緒池時,最終會呼叫具有 7 個引數的構造方法,

程式碼如下。








public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler rejectHandler)

接下來,對 ThreadPoolExecutor 類構造方法中每個引數的具體含義進行簡單的介紹。

(1)corePoolSize 引數。表示執行緒池的核心執行緒數。

(2)maximumPoolSize 引數。表示執行緒池中的最大執行緒數。

(3)keepAliveTime 引數。表示執行緒沒有任務執行狀態保持的最長時間。當執行緒池中的執行緒數量大於 corePoolSize 時,如果沒有新的任務提交,則核心執行緒外的執行緒不會立即銷燬,需要等待,直到等待的時間超過 keepAliveTime 才會終止。

(4)unit 引數。表示 keepAliveTime 的時間單位。

(5)workQueue 引數。表示執行緒池中的阻塞佇列,儲存等待執行的任務。

(6)threadFactory 引數。執行緒工廠,用來建立執行緒池中的執行緒。提供一個預設的執行緒工廠來建立執行緒,當使用預設的執行緒工廠建立執行緒時,會為執行緒設定一個名稱,使新建立的執行緒具有相同的優先順序,並且是非守護執行緒。 

(7)rejectHandler 引數。表示拒絕處理任務時的策略。當 workQueue 阻塞佇列已滿、執行緒池中的執行緒數已經達到最大,且執行緒池中沒有空閒執行緒時,如果繼續提交任務,就需要採取一種策略來處理這個任務。

其中,在 ThreadPoolExecutor 類的構造方法中,最重要的 3 個引數是 corePoolSize、maximumPoolSize 和 workQueue,這 3 個引數會對執行緒池的執行過程產生重大的影響。

三者的關係如下

  • 如果執行緒池中執行的執行緒數小於 corePoolSize,則直接建立新執行緒處理任務,即使執行緒池中的其他執行緒是空閒的。

  • 如果執行的執行緒數大於或等於 corePoolSize 並且小於 maximumPoolSize,則只有當workQueue 佇列滿時,才會建立新的執行緒處理任務。如果 workQueue 佇列不滿,則將新提交的任務放入 workQueue 佇列中。當設定的 corePoolSize 與 maximumPoolSize 相同時,建立的執行緒池大小是固定的,如果滿足有新任務提交、執行緒池中沒有空閒執行緒,且 workQueue 未滿的條件,就把請求放入workQueue,等待空閒的執行緒從 workQueue 中取出任務進行處理。

  • 如果執行的執行緒數量大於 maximumPoolSize,同時 workQueue 已滿,則透過拒絕策略參數 rejectHandler 來指定處理策略。

    執行緒池提供了 4 種拒絕策略,分別如下。

  • 直接丟擲異常,這也是預設的策略。實現類為 AbortPolicy。

  • 使用呼叫者所在的執行緒來執行任務。實現類為 CallerRunsPolicy。

  • 丟棄佇列中最靠前的任務並執行當前任務。實現類為 DiscardOldestPolicy。

  • 直接丟棄當前任務。實現類為 DiscardPolicy。

本文節選自《深入理解高併發程式設計:JDK核心技術》一書,本書是冰河編寫的專注介紹JDK高併發程式設計技術的書籍。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2942860/,如需轉載,請註明出處,否則將追究法律責任。

相關文章