【java學習】ThreadPoolExecutor 執行緒池

兔兔西發表於2018-09-28

1,概念

一個執行緒池管理了一組工作執行緒,同時它還包括了一個用於放置等待執行任務的任務佇列(阻塞佇列)。
預設情況下,在建立了執行緒池後,執行緒池中的執行緒數為0。

Android中的執行緒池來自Java,主要通過Executor來派生特定型別的執行緒池。
建立一個支援定時及週期性的任務執行的執行緒池,多數情況下可用來替代Timer 類。

1.1 Executor介面

Executor 是一個頂層介面,在它裡面只宣告瞭一個方法execute(Runnable),返回值為void,引數為Runnable 型別,從字面意思可以理解,就是用來執行傳進去的任務的;
真正的執行緒池的實現為ThreadPoolExecutor。

1.2 Executors 類

1.3 ExecutorService 介面

繼承了Executor 介面,並宣告瞭一些方法:submit、invokeAll、invokeAny 以及shutDown 等;

1.4 AbstractExecutorService 抽象類

實現了ExecutorService 介面,基本實現了ExecutorService 中宣告的所有方法;

1.5 ThreadPoolExecutor 類

繼承了類AbstractExecutorService。

2,優點

①重用執行緒池中的執行緒
避免因為執行緒的建立和銷燬所帶來的效能開銷。
②有效控制執行緒池的最大併發數
避免大量的執行緒之間因互相搶佔系統資源而導致的阻塞現象。
如果不使用執行緒池,有可能造成系統建立大量執行緒而導致消耗完系統記憶體。
③提高執行緒的可管理性。
使用執行緒池可以對執行緒進行統一的分配和監控,提供定時執行、指定間隔迴圈執行等功能。
④提高響應速度。
當任務到達時,任務可以不需要等到執行緒建立就可以立即執行。

3,引數配置

ThreadPoolExecutor 的構造方法提供了一系列引數來配置執行緒池。

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

–corePoolSize
執行緒池的核心執行緒數。
預設情況下,核心執行緒線上程池中一直存活,即使處於閒置狀態。
通過設定ThreadPoolExecutor的allowCoreThreadTimeOut屬性為true,可以讓閒置的核心執行緒在等待新任務時有超時策略。這個時間間隔由keepAliveTime所指定,超時後,執行緒終止。

–maximumPoolSize
執行緒池所能容納的最大執行緒數。
當活動執行緒數達到此值,後續新任務將會被阻塞。

–keepAliveTime
非核心執行緒閒置時的超時時長。
超過此時長,非核心執行緒就會被回收。
當ThreadPoolExecutor的allowCoreThreadTimeOut屬性設定為true時,可作用於核心執行緒。

–unit
用於指定keepAliveTime引數的時間單位,這是一個列舉,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分鐘)。

–workQueue
執行緒池中的任務佇列,通過執行緒池的execute方法提交的Runnable物件會儲存在這個引數中。

–threadFactory
執行緒工廠,為執行緒池提供建立新執行緒的功能。
ThreadFactory是一個介面,它只有一個方法:Thread newThread(Runnable r)。

–RejectedExecutionHandler handler
這個引數不常用。
當執行緒無法執行新任務時,可能原因:由於任務佇列已滿或者是無法成果執行任務。這個時候ThreadPoolExecutor會呼叫handler的rejectedExecution方法來通知呼叫者。

4,處理策略(核心執行緒corePoolSize 、任務佇列workQueue、最大執行緒maximumPoolSize

當任務提交給執行緒池之後的處理策略如下:

  1. 如果此時執行緒池中的數量小於corePoolSize(核心池的大小),即使執行緒池中的執行緒都處於空閒狀態,也要建立新的執行緒來處理被新增的任務(也就是每來一個任務,就要建立一個執行緒來執行任務)。
  2. 如果此時執行緒池中的數量大於等於corePoolSize,但是緩衝佇列workQueue 未滿,那麼任務被放入緩衝佇列,則該任務會等待空閒執行緒將其取出去執行。
  3. 如果此時執行緒池中的數量大於等於corePoolSize , 緩衝佇列workQueue 滿,並且執行緒池中的數量小於maximumPoolSize(執行緒池最大執行緒數),建新的執行緒來處理被新增的任務。
  4. 如果此時執行緒池中的數量大於等於corePoolSize , 緩衝佇列workQueue 滿,並且執行緒池中的數量等於maximumPoolSize,那麼通過RejectedExecutionHandler 所指定的策略(任務拒絕策略)來處理此任務。也就是處理任務的優先順序為: 核心執行緒corePoolSize 、任務佇列workQueue、最大執行緒maximumPoolSize,如果三者都滿了,使用handler 處理被拒絕的任務。
  5. 特別注意,在corePoolSize 和maximumPoolSize 之間的執行緒數會被自動釋放。當執行緒池中執行緒數量大於corePoolSize 時,如果某執行緒空閒時間超過keepAliveTime,執行緒將被終止,直至執行緒池中的執行緒數目不大於corePoolSize。這樣,執行緒池可以動態的調整池中的執行緒數。

5,分類

AsyncTask對THREAD_POOL_EXECUTOR這個執行緒池進行了配置。

Android中最常見的4類執行緒池,都是直接或間接地通過配置ThreadPoolExecutor來實現自己的功能特性。

5.1 FixedThreadPool(固定執行緒池)

通過Executors的newFixedThreadPool方法來建立。
是一種執行緒數量固定的執行緒池,不回收空閒執行緒,除非執行緒池關閉了。

FixedThreadPool只有核心執行緒並且這些核心執行緒不會被回收。—>能夠快速相應外界請求。

建立一個可重用固定執行緒數的執行緒池,以共享的無界佇列方式來執行這些執行緒。在任意點,在大多數 nThreads 執行緒會處於處理任務的活動狀態。如果在所有執行緒處於活動狀態時提交附加任務,則在有可用執行緒之前,附加任務將在佇列中等待。如果在關閉前的執行期間由於失敗而導致任何執行緒終止,那麼一個新執行緒將代替它執行後續的任務(如果需要)。在某個執行緒被顯式地關閉之前,池中的執行緒將一直存在。

-newFixedThreadPool與cacheThreadPool差不多,也是能reuse就用,但不能隨時建新的執行緒
-其獨特之處:任意時間點,最多隻能有固定數目的活動執行緒存在,此時如果有新的執行緒要建立,只能放在另外的佇列中等待,直到當前的執行緒中某個執行緒終止直接被移出池子
-從方法的原始碼看,cache池和fixed 池呼叫的是同一個底層池,只不過引數不同:

ExecutorService pool = Executors.newFixedThreadPool(2); //核心執行緒數為2
  //建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面 
  Thread t1 = new MyThread(); 
   t1.setPriority(1);//設定執行緒優先順序
  Thread t2 = new MyThread(); 
  Thread t3 = new MyThread(); 
  Thread t4 = new MyThread(); 
  Thread t5 = new MyThread(); 
  //將執行緒放入池中進行執行 
  pool.execute(t1); 
  pool.execute(t2); 
  pool.execute(t3); 
  pool.execute(t4); 
  pool.execute(t5); 

5.2 CachedThreadPool(快取型池子)

通過Executors的newCachedThreadPool方法來建立。
是一種執行緒數量不定的執行緒池,只有核心執行緒且最大執行緒數為Interger.MAX_VALUE(很大,相當於執行緒數可以任意大)。

當執行緒池中沒有空閒執行緒時,執行緒池會建立新的執行緒來處理新任務。
對於空閒執行緒有超時機制,超過60s閒置執行緒會被回收。注意,放入CachedThreadPool的執行緒不必擔心其結束,超過TIMEOUT不活動,其會自動被終止。
呼叫 execute 將重用以前構造的執行緒(如果執行緒可用),如果現有執行緒沒有可用的,則建立一個新執行緒並新增到池中。

場景:適合執行大量的耗時較少的任務(即生存期較短的非同步型任務)。(大量執行緒處於空閒狀態,會停止,幾乎不佔系統資源)

5.3 ScheduledThreadPool(排程型執行緒池)

通過Executors的newScheduledThreadPool方法來建立。
核心執行緒數量固定,非核心執行緒數量無限制且閒置時會立即回收。
這個池子裡的執行緒可以按schedule依次delay執行,或週期執行。

場景:用於執行定時任務和具有固定週期的重複任務。

5.4 SingleThreadExecutor(單例執行緒)

通過Executors的newSingleThreadPool方法來建立。
執行緒池內部只有一個核心執行緒,它確保所有的任務都在同一個執行緒中按順序執行。
以無界佇列方式來執行該執行緒。(注意,如果因為在關閉前的執行期間出現失敗而終止了此單個執行緒,那麼如果需要,一個新執行緒將代替它執行後續的任務)。

建立一個使用單個 worker 執行緒的 Executor,
可保證順序地執行各個任務,任意時間池中只能有一個執行緒。與其他等效的 newFixedThreadPool(1) 不同,可保證無需重新配置此方法所返回的執行程式即可使用其他的執行緒。
用的是和cache池和fixed池相同的底層池,但執行緒數目是1-1,0秒IDLE(無IDLE)

優點:統一所有的外界任務到一個執行緒中,這使得在這些任務之間不需要處理執行緒同步的問題。

6,風險

雖然執行緒池是構建多執行緒應用程式的強大機制,但使用它並不是沒有風險的。
①執行緒池的大小。
多執行緒應用並非執行緒越多越好,需要根據系統執行的軟硬體環境以及應用本身的特點決定執行緒池的大小。一般來說,如果程式碼結構合理的話,執行緒數目與CPU
數量相適合即可。如果執行緒執行時可能出現阻塞現象,可相應增加池的大小;如有必要可採用自適應演算法來動態調整執行緒池的大小,以提高CPU 的有效利用率和系統的整體效能。
②併發錯誤。
多執行緒應用要特別注意併發錯誤,要從邏輯上保證程式的正確性,注意避免死鎖現象的發生。
③執行緒洩漏。
這是執行緒池應用中一個嚴重的問題,當任務執行完畢而執行緒沒能返回池中就會發生執行緒洩漏現象。

7,常用方法

ThreadPoolExecutor 類常用方法:

7.1 execute()

啟動執行緒池。有返回值。

7.2 submit()

啟動執行緒池。有返回值。
submit 方便異常的處理。如果任務可能會丟擲異常,而且希望外面的呼叫者能夠感知這些異常,那麼就需要呼叫submit 方法,通過捕獲Future.get丟擲的異常。

7.3 shutdown()

關閉執行緒池。
此方法執行後不得向執行緒池再提交任務,如果有空閒執行緒則銷燬空閒執行緒,等待所有正在執行的任務及位於阻塞佇列中的任務執行結束,然後銷燬所有執行緒。

7.4 shutdownNow()

關閉執行緒池。
此方法執行後不得向執行緒池再提交任務,如果有空閒執行緒則銷燬空閒執行緒,取消所有位於阻塞佇列中的任務,並將其放入List<Runnable>容器,作為返回值。取消正在執行的執行緒(實際上僅僅是設定正在執行執行緒的中斷標誌位,呼叫執行緒的interrupt 方法來中斷執行緒)。

8,執行緒池配置

8.1 CPU 密集型任務(CPU核心數+1)

可以少配置執行緒數,大概和機器的cpu 核數相當,可以使得每個執行緒都在執行任務。
因為CPU密集型任務使得CPU使用率很高,若開過多的執行緒數,只能增加上下文切換的次數,因此會帶來額外的開銷。

8.2 IO 密集型任務(2*CPU核心數)

由於需要等待IO 操作,執行緒並不是一直在執行任務,則配置儘可能多的執行緒,2*cpu 核數。
IO密集型任務CPU使用率並不高,因此可以讓CPU在等待IO的時候去處理別的任務,充分利用CPU時間。

8.3 有界佇列和無界佇列的配置

需區分業務場景,一般情況下配置有界佇列,在一些可能會有爆發性增長的情況下使用無界佇列。

8.4 任務非常多時

使用非阻塞佇列使用CAS 操作替代鎖可以獲得好的吞吐量。synchronousQueue 吞吐率最高。

9,效能優化

1)避免建立過多的物件
2)不要過多使用列舉
列舉佔用的記憶體空間比整型大。
3)常量請使用static final來修飾
4)使用一些Android特有的有更好效能的資料結構
如SparseArray、Pair等。
5)適當使用軟引用和弱引用
6)採用記憶體快取和磁碟快取
7)儘量採用靜態內部類
避免由於內部類OOM。

相關文章