1. 程式人生 > 實用技巧 >Java執行緒池詳解

Java執行緒池詳解

轉載:https://www.jianshu.com/p/7726c70cdc40


1、執行緒池的優勢

(1)、降低系統資源消耗,通過重用已存在的執行緒,降低執行緒建立和銷燬造成的消耗;
(2)、提高系統響應速度,當有任務到達時,通過複用已存在的執行緒,無需等待新執行緒的建立便能立即執行;
(3)方便執行緒併發數的管控。因為執行緒若是無限制的建立,可能會導致記憶體佔用過多而產生OOM,並且會造成cpu過度切換(cpu切換執行緒是有時間成本的(需要保持當前執行執行緒的現場,並恢復要執行執行緒的現場))。
(4)提供更強大的功能,延時定時執行緒池。

2、執行緒池的主要引數

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

1、corePoolSize(執行緒池基本大小):當向執行緒池提交一個任務時,若執行緒池已建立的執行緒數小於corePoolSize,即便此時存在空閒執行緒,也會通過建立一個新執行緒來執行該任務,直到已建立的執行緒數大於或等於corePoolSize時,(除了利用提交新任務來建立和啟動執行緒(按需構造),也可以通過 prestartCoreThread() 或 prestartAllCoreThreads() 方法來提前啟動執行緒池中的基本執行緒。)

2、maximumPoolSize(執行緒池最大大小):執行緒池所允許的最大執行緒個數。當佇列滿了,且已建立的執行緒數小於maximumPoolSize,則執行緒池會建立新的執行緒來執行任務。另外,對於無界佇列,可忽略該引數。

3、keepAliveTime(執行緒存活保持時間)當執行緒池中執行緒數大於核心執行緒數時,執行緒的空閒時間如果超過執行緒存活時間,那麼這個執行緒就會被銷燬,直到執行緒池中的執行緒數小於等於核心執行緒數。

4、workQueue(任務佇列):用於傳輸和儲存等待執行任務的阻塞佇列。

5、threadFactory(執行緒工廠):用於建立新執行緒。threadFactory建立的執行緒也是採用new Thread()方式,threadFactory建立的執行緒名都具有統一的風格:pool-m-thread-n(m為執行緒池的編號,n為執行緒池內的執行緒編號)。

5、handler(執行緒飽和策略):當執行緒池和佇列都滿了,再加入執行緒會執行此策略。

3、執行緒池流程

執行緒池流程

1、判斷核心執行緒池是否已滿,沒滿則建立一個新的工作執行緒來執行任務。已滿則。
2、判斷任務佇列是否已滿,沒滿則將新提交的任務新增在工作佇列,已滿則。
3、判斷整個執行緒池是否已滿,沒滿則建立一個新的工作執行緒來執行任務,已滿則執行飽和策略。

(1、判斷執行緒池中當前執行緒數是否大於核心執行緒數,如果小於,在建立一個新的執行緒來執行任務,如果大於則
2、判斷任務佇列是否已滿,沒滿則將新提交的任務新增在工作佇列,已滿則。
3、判斷執行緒池中當前執行緒數是否大於最大執行緒數,如果小於,則建立一個新的執行緒來執行任務,如果大於,則執行飽和策略。)

4、執行緒池為什麼需要使用(阻塞)佇列?

回到了非執行緒池缺點中的第3點:
1、因為執行緒若是無限制的建立,可能會導致記憶體佔用過多而產生OOM,並且會造成cpu過度切換。

另外回到了非執行緒池缺點中的第1點:
2、建立執行緒池的消耗較高。
或者下面這個網上並不高明的回答:
2、執行緒池建立執行緒需要獲取mainlock這個全域性鎖,影響併發效率,阻塞佇列可以很好的緩衝。

5、執行緒池為什麼要使用阻塞佇列而不使用非阻塞佇列?

阻塞佇列可以保證任務佇列中沒有任務時阻塞獲取任務的執行緒,使得執行緒進入wait狀態,釋放cpu資源。
當佇列中有任務時才喚醒對應執行緒從佇列中取出訊息進行執行。
使得線上程不至於一直佔用cpu資源。

(執行緒執行完任務後通過迴圈再次從任務佇列中取出任務進行執行,程式碼片段如下
while (task != null || (task = getTask()) != null) {})。

不用阻塞佇列也是可以的,不過實現起來比較麻煩而已,有好用的為啥不用呢?

6、如何配置執行緒池

CPU密集型任務
儘量使用較小的執行緒池,一般為CPU核心數+1。 因為CPU密集型任務使得CPU使用率很高,若開過多的執行緒數,會造成CPU過度切換。

IO密集型任務
可以使用稍大的執行緒池,一般為2*CPU核心數。 IO密集型任務CPU使用率並不高,因此可以讓CPU在等待IO的時候有其他執行緒去處理別的任務,充分利用CPU時間。

混合型任務
可以將任務分成IO密集型和CPU密集型任務,然後分別用不同的執行緒池去處理。 只要分完之後兩個任務的執行時間相差不大,那麼就會比序列執行來的高效。
因為如果劃分之後兩個任務執行時間有資料級的差距,那麼拆分沒有意義。
因為先執行完的任務就要等後執行完的任務,最終的時間仍然取決於後執行完的任務,而且還要加上任務拆分與合併的開銷,得不償失。

7、java中提供的執行緒池

Executors類提供了4種不同的執行緒池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor

java執行緒池對比

1、newCachedThreadPool:用來建立一個可以無限擴大的執行緒池,適用於負載較輕的場景,執行短期非同步任務。(可以使得任務快速得到執行,因為任務時間執行短,可以很快結束,也不會造成cpu過度切換)

2、newFixedThreadPool:建立一個固定大小的執行緒池,因為採用無界的阻塞佇列,所以實際執行緒數量永遠不會變化,適用於負載較重的場景,對當前執行緒數量進行限制。(保證執行緒數可控,不會造成執行緒過多,導致系統負載更為嚴重)

3、newSingleThreadExecutor:建立一個單執行緒的執行緒池,適用於需要保證順序執行各個任務。

4、newScheduledThreadPool:適用於執行延時或者週期性任務。

8、execute()和submit()方法

1、execute(),執行一個任務,沒有返回值。
2、submit(),提交一個執行緒任務,有返回值。
submit(Callable<T> task)能獲取到它的返回值,通過future.get()獲取(阻塞直到任務執行完)。一般使用FutureTask+Callable配合使用(IntentService中有體現)。

submit(Runnable task, T result)能通過傳入的載體result間接獲得執行緒的返回值。
submit(Runnable task)則是沒有返回值的,就算獲取它的返回值也是null。

Future.get方法會使取結果的執行緒進入阻塞狀態,知道執行緒執行完成之後,喚醒取結果的執行緒,然後返回結果。

https://www.cnblogs.com/dolphin0520/p/3949310.html



作者:小紅軍storm
連結:https://www.jianshu.com/p/7726c70cdc40
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。