Java線程池詳解

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

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

9、阻塞隊列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章