好綱要用在刀刃上,適時選擇不同的線程池來實現

Java的線程池實現從根本上來說只有兩個:ThreadPoolExecutor類和ScheduledThreadPoolExecutor類,這兩個類還是父子關係,但是Java為了簡化並行計算,還提供了一個Exceutors的靜態類,它可以直接生成多種不同的線程池執行器,比如單線程執行器、帶緩衝功能的執行器等,但歸根結底還是使用ThreadPoolExecutor類或ScheduledThreadPoolExecutor類的封裝類。

為了理解這些執行器,我們首先來看看ThreadPoolExecutor類,其中它複雜的構造函數可以很好的理解線程池的作用,代碼如下:

好綱要用在刀刃上,適時選擇不同的線程池來實現

這是ThreadPoolExecutor最完整的構造函數,其他的構造函數都是引用該構造函數實現的,我們逐步來解釋這些參數的含義。

  1. corePoolSize:最小線程數。線程啟動後,在池中保持線程的最小數量。需要說明的是線程數量是逐步到達corePoolSize值的,例如corePoolSize被設置為10,而任務數量為5,則線程池中最多會啟動5個線程,而不是一次性的啟動10個線程。

  2. maximumPoolSize:最大線程數量。這是池中最大能容納的最大線程數量,如果超出,則使用RejectedExecutionHandler 拒絕策略處理。

  3. keepAliveTime:線程最大生命週期。這裡的生命週期有兩個約束條件,一是該參數針對的是超過corePoolSize數量的線程。二是處於非運行狀態的線程。這麼說吧,如果corePoolSize為10,maximumPoolSize為20,此時線程池中有15個線程正在運行,一段時間後,其中有3個線程處於等待狀態的時間超過了keepAliveTime指定的時間,則結束這3個線程,此時線程池中還有12個線程正在運行。

  4. unit:時間單位。這是keepAliveTime的時間單位,可以是納秒、毫秒、秒、分等選項。

  5. workQuene:任務隊列。當線程池中的線程都處於運行狀態,而此時任務數量繼續增加,則需要一個容器來容納這些任務,這就是任務隊列。

  6. threadFactory:線程工廠。定義如何啟動一個線程,可以設置線程名稱,並且可以確認是否是後臺線程等。

  7. handler:拒絕任務處理器。由於超出線程數量和隊列容量而對繼續增加的任務進行處理的程序。

線程池的管理是這樣一個過程:首先創建線程池,然後根據任務的數量逐步將線程增大到corePoolSize數量,如果此時仍有任務增加,則放置到workQuene中,直到workQuene爆滿為止,然後繼續增加池中的數量(增強處理能力),最終達到maximumPoolSize,那如果此時還有任務增加進來呢?這就需要handler處理了,或者丟棄任務,或者拒絕新任務,或者擠佔已有任務等。

在任務隊列和線程池都飽和的情況下,一但有線程處於等待(任務處理完畢,沒有新任務增加)狀態的時間超過keepAliveTime,則該線程終止,也就說池中的線程數量會逐漸降低,直至為corePoolSize數量為止。

我們可以把線程池想象為這樣一個場景:在一個生產線上,車間規定是可以有corePoolSize數量的工人,但是生產線剛建立時,工作不多,不需要那麼多的人。隨著工作數量的增加,工人數量也逐漸增加,直至增加到corePoolSize數量為止。此時還有任務增加怎麼辦呢?

好辦,任務排隊,corePoolSize數量的工人不停歇的處理任務,新增加的任務按照一定的規則存放在倉庫中(也就是我們的workQuene中),一旦任務增加的速度超過了工人處理的能力,也就是說倉庫爆滿時,車間就會繼續招聘工人(也就是擴大線程數),直至工人數量到達maximumPoolSize為止,那如果所有的maximumPoolSize工人都在處理任務時,而且倉庫也是飽和狀態,新增任務該怎麼處理呢?這就會扔一個叫handler的專門機構去處理了,它要麼丟棄這些新增的任務,要麼無視,要麼替換掉別的任務。

過了一段時間後,任務的數量逐漸減少,導致一部分工人處於待工狀態,為了減少開支(Java是為了減少系統的資源消耗),於是開始辭退工人,直至保持corePoolSize數量的工人為止,此時即使沒有工作,也不再辭退工人(池中的線程數量不再減少),這也是保證以後再有任務時能夠快速的處理。

明白了線程池的概念,我們再來看看Executors提供的幾個線程創建線程池的便捷方法:

  • newSingleThreadExecutor:單線程池。顧名思義就是一個池中只有一個線程在運行,該線程永不超時,而且由於是一個線程,當有多個任務需要處理時,會將它們放置到一個無界阻塞隊列中逐個處理,它的實現代碼如下:

好綱要用在刀刃上,適時選擇不同的線程池來實現

它的使用方法也很簡單,下面是簡單的示例:

好綱要用在刀刃上,適時選擇不同的線程池來實現

  • newCachedThreadPool:緩衝功能的線程。建立了一個線程池,而且線程數量是沒有限制的(當然,不能超過Integer的最大值),新增一個任務即有一個線程處理,或者複用之前空閒的線程,或者重親啟動一個線程,但是一旦一個線程在60秒內一直處於等待狀態時(也就是一分鐘無事可做),則會被終止,其源碼如下:

好綱要用在刀刃上,適時選擇不同的線程池來實現

這裡需要說明的是,任務隊列使用了同步阻塞隊列,這意味著向隊列中加入一個元素,即可喚醒一個線程(新創建的線程或複用空閒線程來處理),這種隊列已經沒有隊列深度的概念了.

  • newFixedThreadPool:固定線程數量的線程池。 在初始化時已經決定了線程的最大數量,若任務添加的能力超出了線程的處理能力,則建立阻塞隊列容納多餘的任務,其源碼如下:

好綱要用在刀刃上,適時選擇不同的線程池來實現

上面返回的是一個ThreadPoolExecutor,它的corePoolSize和maximumPoolSize是相等的,也就是說,最大線程數量為nThreads。如果任務增長的速度非常快,超過了LinkedBlockingQuene的最大容量(Integer的最大值),那此時會如何處理呢?會按照ThreadPoolExecutor默認的拒絕策略(默認是DiscardPolicy,直接丟棄)來處理。

以上三種線程池執行器都是ThreadPoolExecutor的簡化版,目的是幫助開發人員屏蔽過得線程細節,簡化多線程開發。當需要運行異步任務時,可以直接通過Executors獲得一個線程池,然後運行任務,不需要關注ThreadPoolExecutor的一系列參數是什麼含義。當然,有時候這三個線程不能滿足要求,此時則可以直接操作ThreadPoolExecutor來實現複雜的多線程計算。可以這樣比喻,newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool是線程池的簡化版,而ThreadPoolExecutor則是旗艦版___簡化版容易操作,需要了解的知識相對少些,方便使用,而旗艦版功能齊全,適用面廣,難以駕馭。

有討論,才有進步,大家各抒己見,讓每位同學學到不一樣的!


分享到:


相關文章: