目錄
- 一、簡介
- 二、線程池任務場景
- 場景一:提交5個任務,執行總耗時500ms
- 場景二:提交10個任務,執行總耗時500ms
- 場景三:提交11個任務,執行總耗時1000ms
- 場景四:提交20個任務,執行總耗時1000ms
- 場景五:提交30個任務,執行總耗時1500ms
- 場景六:提交40個任務,執行總耗時2000ms
- 場景七:提交41個任務,執行總耗時2000ms
- 場景八:提交45個任務,執行總耗時1500ms
- 場景九:提交50個任務,執行總耗時1500ms
- 場景十:提交51個任務,執行總耗時1500ms
- 三、總結
線程池原理詳見:Java基礎——深入理解Java線程池
一、簡介
網上有很多關于 線程池原理
的講解,理論說的多了你也不一定記得住,也不一定理解的了,下面我通過一個DEMO,加上多個任務場景,讓你能夠直觀的理解線程池的工作流程。
二、線程池任務場景
線程池有幾個關鍵參數:核心線程數、最大線程數、回收時間、隊列等。
我們DEMO使用自定義線程池方式來講解線程池的工作流程,代碼如下:
package com.example.springbootdemo.util;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class ThreadPoolTest {// 任務數private static int taskCount = 50;// 實際完成任務數private static AtomicInteger taskCountExecuted;public static void main(String[] args) {init();}private static void init(){taskCountExecuted = new AtomicInteger(0);ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, // 核心線程數20, // 最大線程數5, // 非核心線程回收超時時間TimeUnit.SECONDS, // 超時時間單位new ArrayBlockingQueue<>(30) // 任務隊列);System.out.println("任務總數 = [" + taskCount + "]個");long start = System.currentTimeMillis();for(int i=0; i<taskCount; i++){Runnable runnable = new Runnable() {@Overridepublic void run() {try{Thread.sleep(500);System.out.println("已執行第 [" + taskCountExecuted.addAndGet(1) + "] 個任務");}catch (Exception ex){ex.printStackTrace();}}};try{// 默認拒絕策略會報錯threadPoolExecutor.execute(runnable);}catch (Exception ex){ex.printStackTrace();taskCount = threadPoolExecutor.getActiveCount() + threadPoolExecutor.getQueue().size();}}long end = 0;while (threadPoolExecutor.getCompletedTaskCount() < taskCount){end = System.currentTimeMillis();}System.out.println("[" + taskCountExecuted + "]個任務執行總耗時 = [" + (end - start) + "]ms");threadPoolExecutor.shutdown();}
}
說明:我們DEMO中新建了個線程池,核心線程數10,最大線程數20,任務隊列容量30。
執行每個任務場景只需修改 taskCount
值即可。
場景一:提交5個任務,執行總耗時500ms
taskCount = 5;
執行結果:
任務總數 = [5]個
已執行第 [2] 個任務
已執行第 [3] 個任務
已執行第 [4] 個任務
已執行第 [1] 個任務
已執行第 [5] 個任務
[5]個任務執行總耗時 = [506]ms
分析:
核心線程數為10,也就是說有10個線程數長期處于活動狀態,即來任務立馬就能執行,任務數5 < 核心線程數10,所以,5個任務立馬執行完成,且是多線程并行執行,所以任務執行總耗時 = 500ms
注:我們日志輸出的是 505ms ,因為代碼執行也是要花時間的。
場景二:提交10個任務,執行總耗時500ms
taskCount = 10;
執行結果:
任務總數 = [10]個
已執行第 [2] 個任務
已執行第 [8] 個任務
已執行第 [5] 個任務
...
已執行第 [3] 個任務
已執行第 [9] 個任務
已執行第 [10] 個任務
[10]個任務執行總耗時 = [507]ms
分析:
任務數10 <= 核心線程數10,10個任務立馬執行完成,所以任務執行總耗時 = 500ms。
場景三:提交11個任務,執行總耗時1000ms
taskCount = 11;
執行結果:
任務總數 = [11]個
已執行第 [1] 個任務
已執行第 [3] 個任務
已執行第 [4] 個任務
...
已執行第 [8] 個任務
已執行第 [7] 個任務
已執行第 [11] 個任務
[11]個任務執行總耗時 = [1009]ms
分析:
任務執行總耗時 = 1000ms,別驚訝,這里是很多人沒有搞懂線程池運行機制的關鍵點,雖然任務只多個一個,但是地11個任務不是立馬執行的,核心線程數為10,第11個任務會進入到任務隊列,等核心線程有空出來后會從任務隊列中取出任務再來執行,因此任務總耗時 = 1000ms。
場景四:提交20個任務,執行總耗時1000ms
taskCount = 20;
執行結果:
任務總數 = [20]個
已執行第 [5] 個任務
已執行第 [4] 個任務
已執行第 [2] 個任務
...
已執行第 [16] 個任務
已執行第 [19] 個任務
已執行第 [20] 個任務
[20]個任務執行總耗時 = [1010]ms
分析:
任務執行總耗時 = 1000ms,此處與前一個場景一樣,第11到第20共10個任務會進入到任務隊列,等核心線程有空出來后會從任務隊列中取出任務再來執行,因為有10個核心線程,前10個任務執行完成后,任務隊列中的10個任務正好由空出的10個核心線程來執行,因此任務總耗時 = 1000ms。
場景五:提交30個任務,執行總耗時1500ms
taskCount = 30;
執行結果:
任務總數 = [30]個
已執行第 [2] 個任務
已執行第 [6] 個任務
已執行第 [3] 個任務
...
已執行第 [23] 個任務
已執行第 [25] 個任務
已執行第 [30] 個任務
[30]個任務執行總耗時 = [1514]ms
分析:
任務執行總耗時 = 1500ms,此處與前一個場景一樣,第11到第30共20個任務會進入到任務隊列,等核心線程有空出來后會從任務隊列中取出任務再來執行,因為有10個核心線程,前10個任務執行完成后,從任務隊列中取出10個任務由空出的10個核心線程來執行,執行完后在取出10個任務來執行,因此任務總耗時 = 1500ms。
場景六:提交40個任務,執行總耗時2000ms
taskCount = 40;
執行結果:
任務總數 = [40]個
已執行第 [1] 個任務
已執行第 [2] 個任務
已執行第 [4] 個任務
...
已執行第 [32] 個任務
已執行第 [34] 個任務
已執行第 [40] 個任務
[40]個任務執行總耗時 = [2016]ms
分析:
任務執行總耗時 = 2000ms,此處與前一個場景一樣,這里不再過多解釋。
場景七:提交41個任務,執行總耗時2000ms
taskCount = 41;
執行結果:
任務總數 = [41]個
已執行第 [1] 個任務
已執行第 [2] 個任務
已執行第 [3] 個任務
...
已執行第 [40] 個任務
已執行第 [37] 個任務
已執行第 [41] 個任務
[41]個任務執行總耗時 = [2016]ms
分析:
任務執行總耗時 = 2000ms,這里重點來了,我們知道第11到第40共30個任務進入到了任務隊列中(任務隊列大小為30),第41個任務就創建了一個非核心線程(最大線程數20 - 核心線程數10 = 10個非核心線程)來執行,此時線程池中的活躍線程數為11,第一批任務執行完后,會從任務隊列中取出11個任務來執行,那就是 11 + 11 + 11 + 8 = 500ms * 4 = 2000ms。
場景八:提交45個任務,執行總耗時1500ms
taskCount = 45;
執行結果:
任務總數 = [45]個
已執行第 [3] 個任務
已執行第 [4] 個任務
...
已執行第 [43] 個任務
已執行第 [42] 個任務
已執行第 [45] 個任務
[45]個任務執行總耗時 = [1516]ms
分析:
任務執行總耗時 = 1500ms,此處與前一個場景一樣,我們知道第11到第40共30個任務進入到了任務隊列中(任務隊列大小為30),第41到第45共5個任務就創建了5個非核心線程(最大線程數20 - 核心線程數10 = 10個非核心線程)來執行,此時線程池中的活躍線程數為15,第一批任務執行完后,會從任務隊列中取出15個任務來執行,那就是 15 + 15 + 15 = 500ms * 3 = 1500ms。
場景九:提交50個任務,執行總耗時1500ms
taskCount = 50;
執行結果:
任務總數 = [50]個
已執行第 [1] 個任務
已執行第 [9] 個任務
已執行第 [12] 個任務
...
已執行第 [45] 個任務
已執行第 [44] 個任務
已執行第 [50] 個任務
[50]個任務執行總耗時 = [1515]ms
分析:
任務執行總耗時 = 1500ms,此處與前一個場景一樣,我們知道第11到第40共30個任務進入到了任務隊列中(任務隊列大小為30),第41到第50共10個任務就創建了10個非核心線程(最大線程數20 - 核心線程數10 = 10個非核心線程)來執行,此時線程池中的活躍線程數為20,第一批任務執行完后,會從任務隊列中取出20個任務來執行,那就是 20 + 20 + 10 = 500ms * 3 = 1500ms。
場景十:提交51個任務,執行總耗時1500ms
taskCount = 10;
執行結果:
任務總數 = [51]個
java.util.concurrent.RejectedExecutionException: Task com.example.springbootdemo.util.ThreadPoolTest$1@682a0b20 rejected from java.util.concurrent.ThreadPoolExecutor@3d075dc0[Running, pool size = 20, active threads = 20, queued tasks = 30, completed tasks = 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)at com.example.springbootdemo.util.ThreadPoolTest.init(ThreadPoolTest.java:48)at com.example.springbootdemo.util.ThreadPoolTest.main(ThreadPoolTest.java:16)
已執行第 [3] 個任務
已執行第 [5] 個任務
...
已執行第 [49] 個任務
已執行第 [48] 個任務
已執行第 [50] 個任務
[50]個任務執行總耗時 = [1514]ms
分析:
任務執行總耗時 = 1500ms,此處與前一個場景一樣,由于線程池同時最大能接收50個任務(最大線程數20 + 任務隊列大小30 = 50),所以第51個任務被拒絕了(線程池使用默認拒絕策略AbortPolicy),拋出了異常,DEMO中使用了try-catch捕獲到了。
三、總結
-
任務數
<=核心線程數
,線程池中工作線程數
=任務數
; -
核心線程數
<任務數
<= (核心線程數 + 隊列容量
)時,線程池中工作線程數
=核心線程數
; -
(
核心線程數 + 隊列容量
) <任務數
<= (最大線程數 + 隊列容量
)時,線程池中工作線程數
= (任務數 - 隊列容量
);