一個DEMO讓你徹底理解線程池

目錄

  • 一、簡介
  • 二、線程池任務場景
    • 場景一:提交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捕獲到了。


三、總結

  • 任務數 <= 核心線程數,線程池中工作線程數 = 任務數

  • 核心線程數 < 任務數 <= (核心線程數 + 隊列容量)時,線程池中工作線程數 = 核心線程數

  • 核心線程數 + 隊列容量) < 任務數 <= (最大線程數 + 隊列容量)時,線程池中工作線程數 = (任務數 - 隊列容量);


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446723.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446723.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446723.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++primer第九章 順序容器 9.1 順序容器概述 9.2容器庫概覽

一個容器就是一些特定類型對象的集合。順序容器(sequentialcontainer)為程序員提供了控制元素存儲和訪問順序的能力。這種順序不依賴于元素的值&#xff0c;而是與元素加入容器時的位置相對應。與之相對的&#xff0c;我們將在第11章介紹的有序和無序關聯容器&#xff0c;則根據…

SpringBoot 啟動報錯:Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb

目錄一、報錯日志二、原因分析三、問題排查四、解決方案方案一&#xff1a;如果項目不需要數據庫相關信息就排除此類的autoconfig方案二&#xff1a;配置文件添加數據庫鏈接信息方案三&#xff1a;配置pom.xml中yml或者properties掃描一、報錯日志 **************************…

codeforces 339A-C語言解題報告

339A題目網址 題目解析 1.輸入如321的式子,升序排序(從小到大)成123 舉例: 輸入: 11313 輸出: 11133 2.對字符串進行排序采取拍冒泡排序算法 char c0; for(i0;i<strlen(s)-1;i) {for(j0;j<strlen(s)-1;j){if(s[j]>s[j1]){cs[j];s[j]s[j1];s[j1]c;}} }代碼 #includ…

C++primer第九章 順序容器 9.3 順序容器操作

9.3順序容器操作 順序容器和關聯容器的不同之處在于兩者組織元素的方式。這些不同之處直接關系到了元素如何存儲、訪問、添加以及刪除。上一節介紹了所有容器都支持的操作&#xff08;羅列于表9.2&#xff08;第295頁&#xff09;&#xff09;。本章剩余部分將介紹順序容器所特…

SpringBoot 集成Nacos報錯(一)

目錄配置信息報錯信息解決方案配置信息 <project><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.2</version><relativePath/></parent>…

C++primer第九章 順序容器 9.4 vector對象是如何增長的

為了支持快速隨機訪問&#xff0c;vector將元素連續存儲&#xff0c;每個元素緊挨著前一個元素存儲。通常情況下&#xff0c;我們不必關心一個標準庫類型是如何實現的&#xff0c;而只需關心它如何使用。然而&#xff0c;對于vector和string,其部分實現滲透到了接口中。假定容器…

codeforces 281A-C語言解題報告

281A題目網址 題目解析 1.字符串首字母大寫 代碼 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> int main() {char s[1000]{\0};scanf("%s",s);if(s[0]>A&&s[0]<Z){printf("%s",s…

SpringBoot 配置文件bootstrap和application的區別

目錄一、SpringBoot配置文件二、bootstrap和application區別三、bootstrap和application的應用場景一、SpringBoot配置文件 bootstrap&#xff08;.yml 或者 .properties&#xff09; application&#xff08;.yml 或者 .properties&#xff09; 二、bootstrap和application區…

C++primer第九章 順序容器 9.5 額外的string操作

除了順序容器共同的操作之外&#xff0c;string類型還提供了一些額外的操作。這些操作中 的大部分要么是提供string類和C 風格字符數組之間的相互轉換,要么是增加了允許我們用下標代替迭代器的版本。標準庫string類型定義了大量函數。幸運的是&#xff0c;這些函數使用了重復的…

Zookeeper Mac下安裝操作

目錄一、下載Zookeeper二、修改配置1、設置啟動配置文件2、修改配置三、啟動Zookeeper服務命令1、bin目錄下執行&#xff08;1&#xff09;啟動Zookeeper命令&#xff08;2&#xff09;查看Zookeeper狀態命令&#xff08;3&#xff09;停止Zookeeper命令2、配置環境變量執行&am…

codeforces 266A-C語言解題報告

266A題目網址 題目解析 1.輸入n(1–50)個石頭個數,輸入RGB的石頭顏色,求問拿走最小的石頭個數,讓它們相鄰的石頭顏色不同 代碼 #include<stdio.h> #include<stdlib.h> #include<string.h> int main() {int n,i,count0;char s[50]{\0};scanf("%d&quo…

2014年考研英語二作文PartB圖表題

作文詳細解析 題目 Write an essay based on the following chart, in which you should interpret the chart, and give your comments You should write about 150 words on the ANSWER SHEET.(15 points) 注意點 1.圖表題在第一段描述圖表信息時,一定要寫清楚y軸變化…

Zookeeper 終端命令

目錄一、服務端命令1、啟動Zookeeper服務命令2、查看Zookeeper狀態命令3、停止Zookeeper服務命令4、啟動Zookeeper客戶端命令二、客戶端命令1、查看幫助2、查看當前znode所包含的內容3、創建znode4、創建短暫znode5、創建帶序號znode6、創建短暫帶序號znode7、獲取znode數據8、…

C++primer第九章 順序容器 9.6 容器適配器

9.6容器適配器 除了順序容器外&#xff0c;標準庫還定義了三個順序容器適配器&#xff1a;stack、queue和priority_queue適配器(adaptor)是標準庫中的一個通用概念。容器、迭代器和函數<369I都有適配器。本質上&#xff0c;一個適配器是一種機制&#xff0c;能使某種事物的…

codeforces 236A-C語言解題報告

236題目網址 題目解析 1.輸入字符串,判斷其中不同的字符個數,奇偶輸出不同的語句 2.使用冒泡排序去排序,當遇到s[k]!s[k1]時進行計數 代碼 #include<stdio.h> #include<stdlib.h> #include<string.h> int main() {char s [100]{\0};int i,j,k,count0;cha…

SpringBoot Controller接收參數的常用方式

文章目錄一、請求路徑參數1、PathVariable二、Body參數1、RequestParam2、RequestBody三、請求頭參數和Cookie參數1、RequestHeader2、CookieValue一、請求路徑參數 1、PathVariable 注解為&#xff1a; org.springframework.web.bind.annotation.PathVariable獲取路徑參數&…

C++primer第十章 泛型算法 10.1 概述 10.2 初識泛型算法

大多數算法都定義在頭文件algorithm中。標準庫還在頭文件numeric中定義了 一組數值泛型算法一般情況下&#xff0c;這些算法并不直接操作容器&#xff0c;而是遍歷由兩個迭代器指定的一個元素范圍(參見9.2.1節&#xff0c;第296頁)來進行操作。通常情況下&#xff0c;算法遍歷范…

MySQL Mac安裝教程

文章目錄一、下載安裝包二、安裝三、啟動MySQL四、環境變量設置一、下載安裝包 下載地址&#xff1a;https://downloads.mysql.com/archives/community/ 二、安裝 雙擊安裝包&#xff0c;然后一直點繼續即可。 三、啟動MySQL 打開 系統偏好設置&#xff0c;會發現多了一個…

codeforces 96A-C語言解題報告

96A題目網址 題目解析 1.輸入0和1表示不同隊的隊員字符串,如果7個及以上的一個0或1在一起,則輸出YES否則輸出NO 舉例: 輸入: 1000000001 輸出: YES 2.循環時,當遇到count7時輸出YES并跳出循環,遇到s[i]!s[i1]時,將count重置為1,最后count<7再輸出NO 代碼 #include<s…

C++生成指定范圍內的隨機數

代碼 rand&#xff08;&#xff09;% 3 &#xff1b; 3就是范圍&#xff0c;代表生成[0,3)之間的隨機數 int main(){for (int i 0; i < 20; i) {switch (rand() % 3) {case 0:std::cout << "00" << std::endl;case 1:std::cout << "11&q…