1. 程式人生 > >java執行緒池淺析

java執行緒池淺析

簡介

       執行緒的使用在 java 中佔有極其重要的地位,在 jdk1.4 極其之前的 jdk 版本中,關於執行緒池的使用是極其簡陋的。在 jdk1.5 之後這一情況有了很大的改觀。Jdk1.5 之後加入了java.util.concurrent 包,這個包中主要介紹 java 中執行緒以及執行緒池的使用。為我們在開發中處理執行緒的問題提供了非常大的幫助。

執行緒池

執行緒池的作用:

        執行緒池作用就是限制系統中執行執行緒的數量。根據系統的環境情況,可以自動或手動設定執行緒數量,達到執行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用執行緒池控制執行緒數量,其他執行緒排隊等候。一個任務執行完畢,再從佇列的中取最前面的任務開始執行。若佇列中沒有等待執行緒,執行緒池的這一資源處於等待。當一個新任務需要執行時,如果執行緒池中有等待的工作執行緒,就可以開始運行了,否則進入等待佇列。

為什麼要用執行緒池: :

  1. 減少了建立和銷燬執行緒的次數,每個工作執行緒都可以被重複利用,可執行多個任務。

  2. 可以根據系統的承受能力,調整執行緒池中工作線執行緒的數目,防止因為消耗過多的記憶體,而把伺服器累趴下(每個執行緒需要大約 1MB 記憶體,執行緒開的越多,消耗的記憶體也就越大,最後宕機)。

       Java 裡面執行緒池的頂級介面是 Executor,但是嚴格意義上講 Executor 並不是一個執行緒池,而只是一個執行執行緒的工具。真正的執行緒池介面是 ExecutorService。

 

比較重要的幾個類:

  1. ExecutorService  真正的執行緒池介面。

  2. ScheduledExecutorService  能和 Timer/TimerTask 類似,解決那些需要任務重複執行的問題。

  3. ThreadPoolExecutor  ExecutorService 的預設實現。

  4. ScheduledThreadPoolExecutor 繼承ThreadPoolExecutor的
    ScheduledExecutorService介面實現,週期性任務排程的類實現

 

要配置一個執行緒池是比較複雜的,尤其是對於執行緒池的原理不是很清楚的情況下,很有可能配置的執行緒池不是較優的,因此在 Executors 類裡面提供了一些靜態工廠,生成一些常用的執行緒池

newSingleThreadExecutor

        建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。如果這個唯一的執行緒因為異常結束,那麼會有一個新的執行緒來替代它。此執行緒池保證所有任務的執行順序按照任務的提交順序執行。

newFixedThreadPool

        建立固定大小的執行緒池。每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒

newCachedThreadPool

        建立一個可快取的執行緒池。如果執行緒池的大小超過了處理任務所需要的執行緒,那麼就會回收部分空閒(60 秒不執行任務)的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。此執行緒池不會對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說 JVM)能夠建立的最大執行緒大小。

newScheduledThreadPool

建立一個大小無限的執行緒池。此執行緒池支援定時以及週期性執行任務的需求

例項

newSingleThreadExecutor

執行結果

執行緒名稱:pool-1-thread-1

 newFixedThreadPool

執行結果

一共啟了兩個執行緒:

pool-1-thread-1、pool-1-thread-2

把執行緒數改成5,

執行結果

一共啟動了5個執行緒

pool-1-thread-1、

pool-1-thread-2、

pool-1-thread-3、

pool-1-thread-4、

pool-1-thread-5

newCachedThreadPool

執行結果

一共啟動了5個執行緒

newScheduledThreadPool

執行結果

ThreadPoolExecutor詳解

其中有四個建構函式,但是實質上都是呼叫最後一個,只是前面的三個隊入參進行了初始化。

最終都是呼叫一個建構函式

引數說明:

  • corePoolSize - 池中所儲存的執行緒數,包括空閒執行緒。

  • maximumPoolSize-池中允許的最大執行緒數。

  • keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。

  • unit - keepAliveTime 引數的時間單位。

  • workQueue - 執行前用於保持任務的佇列。此佇列僅保持由 execute 方法提交的 Runnable 任務。

  • threadFactory - 執行程式建立新執行緒時使用的工廠。

  • handler - 由於超出執行緒範圍和佇列容量而使執行被阻塞時所使用的處理程式。

在 JDK 幫助文件中,有如此一段話:

強烈建議程式設計師使用較為方便的 Executors 工廠方法Executors.newCachedThreadPool() (無界執行緒池,可以進行自動線

程回收)、

Executors.newFixedThreadPool(int)(固定大小執行緒池)

Executors.newSingleThreadExecutor() (單個後臺執行緒)

它們均為大多數使用場景預定義了設定

 

下面介紹一下幾個類的原始碼

Executors.newFixedThreadPool (int nThreads): 固定大小執行緒池。

corePoolSize 和 maximumPoolSize 的大小是一樣的(實際上,後

面會介紹,如果使用無界 queue 的話 maximumPoolSize 引數是沒有意義的),keepAliveTime 和 unit 的設值表名什麼?-就是該實現不想 keep alive!最後的BlockingQueue 選擇了 LinkedBlockingQueue,該 queue 有一個特點,他是無界的。

Executors.newSingleThreadExecutor();單執行緒的執行緒池。原始碼中corePoolSize=1,maxmumPoolSize=1

Executors.newCachedThreadPool();無界執行緒池,可以進行自動執行緒回放

這個實現就有意思了。首先是無界的執行緒池,所以我們可以發現 maximumPoolSize為 Integer.max。其次 BlockingQueue 的選擇上使用 SynchronousQueue。可能對於該 BlockingQueue 有些陌生,簡單說:該 QUEUE 中,每個插入操作必須等待另一個執行緒的對應移除操作。

先從 BlockingQueue<Runnable> workQueue 這個入參開始說起。

在 JDK 中,其實已經說得很清楚了,一共有三種類型的 queue。所有 BlockingQueue 都可用於傳輸和保持提交的任務。可以使用此佇列與池大小進行互動:

  • 如果執行的執行緒少於 corePoolSize,則 Executor 始終首選新增新的執行緒,

       而不進行排隊。(如果當前執行的執行緒小於 corePoolSize,則任務根本不         會存放,新增到 queue 中,而是直接抄傢伙(thread)開始執行)。

  • 如果執行的執行緒等於或多於 corePoolSize,則 Executor 始終首選將請求加入佇列, 而不新增新的執行緒。

  • 如果無法將請求加入佇列,則建立新的執行緒,除非建立此執行緒超出 maximumPoolSize,在這種情況下,任務將被拒絕

排隊有三種通用策略:

直接提交、無界佇列、有界佇列

想了解更多敬請關注微信訂閱號