多線程(初階九:線程池)

目錄

一、線程池的由來

二、線程池的簡單介紹

1、ThreadPoolExecutor類

(1)核心線程數和最大線程數:

(2)保持存活時間和存活時間的單位

(3)放任務的隊列

(4)線程工廠

(5)拒絕策略

2、Executors類

3、線程池的執行流程

4、討論線程池中創建多少線程合適

三、線程池的模擬實現

(1)阻塞隊列:存放要執行的任務

(2)submit方法:添加任務的方法,任務添加到隊列中

(3)構造方法:指定創建多少個線程,線程在這個構造方法中都創建好了

(4)存放線程的鏈表:每創建一個線程都放進鏈表中,這樣也能讓我們找到某個線程

(5)最終代碼( + 測試用例)

都看到這了,點個贊再走吧,謝謝謝謝謝


一、線程池的由來

最開始,進程可以解決并發編程的問題,但是這個代價太大了,于是引入了 “輕量級進程” :線程

線程也能解決并發編程的問題,而且線程的開銷比進程要小的多,但是線程如果太多了,創建銷毀線程的頻率進一步提高,此時的線程創建銷毀的開銷就不能忽視了。

為了解決上述問題,大佬們給出了兩個解決方案:

(1)引入 “輕量級線程”:纖程 / 協程

? ? ? ? 協程的本質是程序猿在用戶態代碼中進行調度,不是靠內核的調度器調度的,這樣就節省了很多開銷;協程是在用戶代碼中,基于線程封裝出來的,可能是N個協程對應1個線程,也可能是N個協程對應M個線程。

(2)引入 “線程池”

? ? ? ? 線程池的概念:創建一個線程,這個線程執行完,不會把這個線程給銷毀,而是把這個線程放到線程池中,當我們需要用這個線程的時候,再從線程池中拿,不需要的時候,就放在線程池中,并不會銷毀它;這樣,就省去了頻繁的創建銷毀線程了。

為啥從線程池中取線程 比 從系統中申請線程的創建更高效呢?

? ? ? ? 舉個栗子:

假設在銀行場景中,滑稽老鐵要去這個銀行辦理一個業務,一般銀行中大堂有復印機;這時,滑稽老鐵沒有帶身份證復印件,此時滑稽老鐵要去搞到身份證復印件,有兩個選擇,其一選擇:把身份證給柜員,讓柜員幫滑稽老鐵復印,但是這個操作是不可控的,可能這個柜員中途被老板安排了其他活,那這個時候,就不能幫滑稽老鐵復印身份證了,要等忙完老板安排的活,再幫滑稽老鐵復印身份證;其二選擇:滑稽老鐵自己去大堂中復印身份證,這樣就比較可控了,滑稽老鐵可以很快的去到打印機,立馬復印出來,再去辦理他的業務。如圖:

這里的大堂就是用戶態,柜臺就是內核態,從線程池中取線程,是純用戶態代碼(可控)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???通過系統申請創建線程,需要內核完成(不可控)


二、線程池的簡單介紹

1、ThreadPoolExecutor類

ThreadPoolExecutor參數最多的構造方法,明白了這個構造方法,其他構造方法的參數也就都明白了,如圖:

(1)核心線程數和最大線程數:

corePoolSize核心線程數:正式員工線程

maximumPoolSize最大線程數:正式員工線程 + 實習員工線程

????????舉個栗子:一個公司中有10個正式員工,這10個正式員工是不能隨便開除的,當這10個正式員工忙不過來的時候,公司為了降低成本,會招聘實習員工,而這幾個實習員工是可以隨便開除的,當公司穩定一段時間不忙后,就會開除幾個實習員工。

(2)保持存活時間和存活時間的單位

KeepAliveTime保持存活時間:實習生線程允許摸魚的最大時間

unit存活時間的單位:可以是hour 、 min 、 s 、 ms

(3)放任務的隊列

和定時器類似,線程池中也可以持有多個任務,要執行的任務,使用Runnable來描述任務。

(4)線程工廠

通過這個工廠類創建線程對象(Thread對象),工廠類里面有方法封裝了new Thread的操作,同時給Thread設置了一些屬性,我們想要創建線程的時候可以直接使用工廠類的方法創建。

舉個栗子:

描述一個點,可以用二維坐標和極坐標來表示:二維坐標:(x,y) 極坐標:(r,α)

這里,通過new一個類來得到一個點,這個類里有兩個構造方法,參數分別是(double x,double y),(double r,double α),那么這兩個構造方法的參數類型都一樣,構成不了重載,如圖:

那我們就改方法名不就好了,在使用static修飾,通過不同的方法名獲取類,在方法里new一個類,里面設置一些參數,再返回這個類,如圖:

這樣的的類,就稱為工廠類,工廠類里面得到類的方法就稱為工廠方法。

總的來說,通過靜態方法new了一個對象,在這個靜態方法設置不同的屬性,構造對象的過程,就稱為工廠模式。

(5)拒絕策略

在線程池中有一個阻塞隊列,這個隊列容納線程有上限,如果這個任務隊列滿了,這時有往再添加任務,會發生啥事?

這就引出了拒絕策略,在線程池中,會有四個拒絕策略,如圖:

第一個策略:會直接拋出一個異常,這樣,舊的任務執行不了,新的任務也執行不了

第二個策略:把新的任務丟給添加任務隊列的線程執行,不給入隊列,同時舊的任務依然在執行

第三個策略:把最舊的任務丟棄,添加最新的任務進來

第四個策略:直接把新的任務丟棄了,不執行新的任務,舊的任務會繼續執行

2、Executors類

ThreadPoolExecutor類本身使用起來比較復雜,標準庫給我們提供了另一個版本:把ThreadPoolExecutor封裝了一下,這個類就是Executor類,通過這個類創建出不同的線程池對象,在其內部,已經把ThreadPoolExecutor創建好了,并且設置了一些參數。

Executor的簡單使用,其中主要方法有一下4個,如圖:

我們創建一個固定線程數目的線程池,再往里添加任務

代碼:

public class ThreadPoolTest {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello word");}});}
}

執行結果:

那啥時候使用Executor,啥時候使用ThreadPoolExecutor呢?

網上流傳了 阿里巴巴java開發編程規范,里面寫了不建議使用Executor,而且一定要使用ThreadPoolExecutor,里面說用ThreadPoolExecutor意味著一切都在掌控之中,可以避免一些不必要的因素;我們可以作為參考,不必奉為金科玉律,他們兩各有各的優缺點,這也要以以后入職的公司編程規范為準。

3、線程池的執行流程

(1)當有有任務要讓線程池里面的線程執行時,會比較工作線程數和核心線程數,? ? ? ? ? ? ? ? ? ? ?如果工作線程數 < 核心線程數,則會直接安排線程去執行這個任務。

(2)當工作線程數 > 核心線程數,即線程池中的核心線程數滿了,會添加進阻塞任務隊列中,天氣任務隊列前也會判斷任務隊列是不是空,是空就阻塞等待。

(3)如果線程池中的存活線程數 == 核心線程數,并且阻塞任務隊列也滿了,此時會判斷是否到了最大線程數:maximumPoolSize,如果沒有到達,就會讓非核心線程去執行這個任務。

(4)如果當前線程數到達了最大線程數,則會執行拒絕策略。

4、討論線程池中創建多少線程合適

假設一個進程中,所有線程都是cpu密集型,這時每個線程的工作都是在cpu上執行的,此時,線程池中的數目就不應該超過N(cpu的邏輯核心線程數)

如果一個進程中,所有線程都是IO密集型的,這時每個線程的大部分工作都是在等待IO,此時,線程池中的數目就可以遠遠超過N(cpu的邏輯核心線程數)

上述情況都是極端情況,實際上一個進程中的線程,有cpu密集型的,也有IO密集型的,只是比例不同。由于程序的復雜性,很難直接對線程池進行預估,更準確的做法是通過實驗 / 測試的方法,找出合適的線程數目;也就是嘗試給線程池設定不同的線程,對不同線程情況線程池執行的效率、性能進行評估,找到合適的線程數目。


三、線程池的模擬實現

模擬線程數目固定的線程池

(1)阻塞隊列:存放要執行的任務

代碼:

//阻塞隊列:存放要執行的任務
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);

(2)submit方法:添加任務的方法,任務添加到隊列中

代碼:

//提供submit方法,可以添加任務
public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);
}

(3)構造方法:指定創建多少個線程,線程在這個構造方法中都創建好了

public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {//取出一個任務Runnable runnable = queue.take();//執行任務runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();list.add(t);}}

解析:線程里面,取出一個任務就執行這個任務,如果隊列里沒有任務,就會阻塞等待,等有任務,再執行任務,如此循環往復;每創建一個線程,都要放進鏈表中,也要記得start。

(4)存放線程的鏈表:每創建一個線程都放進鏈表中,這樣也能讓我們找到某個線程

代碼:

//存放線程的鏈表
List<Thread> list = new ArrayList<>();

(5)最終代碼( + 測試用例)

class MyThreadPoolExecutor {//存放線程的鏈表List<Thread> list = new ArrayList<>();//阻塞隊列:存放要執行的任務private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);//提供submit方法,可以添加任務public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {//取出一個任務Runnable runnable = queue.take();//執行任務runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();list.add(t);}}
}
public class MyThreadPoolExecutorTest {public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(4);for (int i = 0; i < 1000; i++) {//變量捕獲int n = i;myThreadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {System.out.println("執行任務:" + n + ",當前線程:" + Thread.currentThread().getName());}});}}
}

測試用例:指定線程池的數目為4個線程,添加1000次任務到阻塞隊列中,讓著4個線程從阻塞隊列中拿任務,再執行任務,任務:打印0~1000,并顯示是哪個線程打印的;

注意:這里我們打印那里我們不能直接放 i ,這里涉及到變量捕獲,不能編譯通過,但他們可以在循環里創建一個變量,把 i 的值賦值給這個變量,再打印 n,這樣每循環一次,都會創建一個成員變量,這個成員變量也不會變,預期也和我們想要預期效果一樣。

執行結果,如圖:

可以看到,并不是順序打印1~1000的,因為不同線程拿到任務的時機不同,多線程執行的順序也是隨機的。


都看到這了,點個贊再走吧,謝謝謝謝謝

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

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

相關文章

Axure的安裝以及簡單使用

目錄 Axure簡介 是什么 有什么用 Axure的優缺點 優點&#xff1a; 缺點&#xff1a; 安裝 漢化 Axure的使用 工具欄 頁面 ?編輯 添加子頁面 ?編輯 Axure簡介 是什么 Axure是一款著名的原型設計工具。它允許用戶創建交互式線框圖、流程圖、原型和其他設計文檔&…

「Verilog學習筆記」脈沖同步電路

專欄前言 本專欄的內容主要是記錄本人學習Verilog過程中的一些知識點&#xff0c;刷題網站用的是牛客網 timescale 1ns/1nsmodule pulse_detect(input clk_fast , input clk_slow , input rst_n ,input data_in ,output dataout );reg data_level, dat…

第十一章 React 封裝自定義組件

一、專欄介紹 &#x1f30d;&#x1f30d; 歡迎加入本專欄&#xff01;本專欄將引領您快速上手React&#xff0c;讓我們一起放棄放棄的念頭&#xff0c;開始學習之旅吧&#xff01;我們將從搭建React項目開始&#xff0c;逐步深入講解最核心的hooks&#xff0c;以及React路由、…

【NLP】RAG 應用中的調優策略

? 檢索增強生成應用程序的調優策略 沒有一種放之四海而皆準的算法能夠最好地解決所有問題。 本文通過數據科學家的視角審視檢索增強生成&#xff08;RAG&#xff09;管道。它討論了您可以嘗試提高 RAG 管道性能的潛在“超參數”。與深度學習中的實驗類似&#xff0c;例如&am…

關于jinja2高版本api變化導致notebook導出html失敗的問題

最新jinja2版本已經到了3.1.2&#xff0c;但是nbconvert引用的應該是老版本&#xff0c;具體代碼報錯如下 Type "help", "copyright", "credits" or "license" for more information. >>> import nbconvert Traceback (most…

spark從表中采樣(隨機選取)一定數量的行

在Spark SQL中&#xff0c;你可以使用TABLESAMPLE來按行數對表進行采樣。以下是使用TABLESAMPLE的示例&#xff1a; SELECT * FROM table_name TABLESAMPLE (1000 ROWS);在這個示例中&#xff0c;table_name是你要查詢的表名。TABLESAMPLE子句后面的(1000 ROWS)表示采樣的行數…

axios 基礎的 一次封裝 二次封裝

一、平常axios的請求發送方式 修改起來麻煩的一批 代碼一大串 二、axios的一次封裝 我們會在src/utils創建一個request.js的文件來存放我們的基地址與攔截器 /* 封裝axios用于發送請求 */ import axios from axios/* (1)request 相當于 Axios 的實例對象 (2)為什么要有reque…

VSCode使用Remote-SSH連接服務器時報錯:無法與“***”建立連接: XHR failed.

關于VSCode的報錯問題&#xff1a;無法與“***”建立連接: XHR failed 問題描述問題理解解決方法手動在本地下載安裝包&#xff0c;然后手動傳到服務器端 問題描述 是的&#xff0c;我又踩坑了&#xff0c;而且這個弄了好久&#xff0c;也重新裝了VSCode軟件&#xff0c;好像結…

2024黑龍江省職業院校技能大賽暨國賽選拔賽“GZ031應用軟件系統開發”賽項賽題題庫

2024黑龍江省職業院校技能大賽暨國賽選拔賽 “GZ031應用軟件系統開發”賽項賽題題庫 2024黑龍江省職業院校技能大賽暨國賽選拔賽 應用軟件系統開發賽項&#xff08;高職組&#xff09; 賽題第1套 目錄 競賽說明 模塊一&#xff1a;系統需求分析 任務1&#xff1a;制造執行…

Kotlin之for循環的具體使用說明

我們用java進行Android開發過程中&#xff0c;經常會用到for循環&#xff0c;在Kotlin中也會經常用到&#xff0c;但是在最近使用Kotlin中我發現&#xff0c;在java中使用for循環不會有什么問題&#xff0c;但是在Kotlin中會出現問題&#xff0c;就是循環出出來的結果不一樣&am…

前端框架(Front-end Framework)和庫(Library)的區別

聚沙成塔每天進步一點點 ? 專欄簡介 前端入門之旅&#xff1a;探索Web開發的奇妙世界 歡迎來到前端入門之旅&#xff01;感興趣的可以訂閱本專欄哦&#xff01;這個專欄是為那些對Web開發感興趣、剛剛踏入前端領域的朋友們量身打造的。無論你是完全的新手還是有一些基礎的開發…

阿里云國際版CDN加速,如何判斷網站IP已加速?

將源站接入阿里云CDN服務后&#xff0c;您可以通過IP檢測功能&#xff0c;檢測客戶端請求實際訪問的IP是否為CDN加速節點IP&#xff0c;判斷加速是否生效。 應用場景 IP檢測的應用場景如下&#xff1a; 場景一&#xff1a;成功配置CDN后&#xff0c;您可以檢測客戶端請求實際…

Android popupwindow在低版本手機上無法顯示

所以我開始看各個參數&#xff0c;注意到了在我自定義popupwindow的builder下的&#xff1a;&#x1f447;&#x1f447; .showAsDropDown(mLinMain, 0, 0);就是這個&#xff0c;這時候我想到了屏幕的原點坐標是&#xff08;0&#xff0c; 0&#xff09;&#xff0c;所設置的p…

Postman高級應用——變量、流程控制、調試、公共函數、外部數據文件

Postman 提供了四種類型的變量 環境變量&#xff08;Environment Variable&#xff09; 不同的環境&#xff0c;使用不同的環境變量&#xff0c;例如&#xff1a;測試過程中經常會用到 測試環境&#xff0c;外網環境等 全局變量&#xff08;Global Variable&#xff09; 所有的…

12.使用 Redis 優化登陸模塊

目錄 1. 使用 Redis 優化登陸模塊 1.1 使用 Redis 存儲驗證碼 1.2 使用 Redis 存儲登錄憑證 1.3 使用 Redis 緩存用戶信息 1. 使用 Redis 優化登陸模塊 使用 Redis 存儲驗證碼&#xff1a;驗證碼需要頻繁的訪問與刷新&#xff0c;對性能要求較高&#xff1b;驗證碼不需要永…

【計算機網絡】序列化,反序列化和初識協議

目錄 ?編輯 一、概念 二、 序列化過程&#xff1a; 選擇序列化格式&#xff1a; 實現序列化代碼&#xff1a; JSON示例&#xff1a; Protocol Buffers示例&#xff1a; JSON編碼示例&#xff1a; 傳輸或存儲&#xff1a; 三、反序列化過程&#xff1a; 下面是反序列…

web前端之中文輸入法導致的高頻事件、addEventListener、compositionstart、compositionend

MENU 代碼compositionendcompositionstartaddEventListener 代碼 html <input type"text" />JavaScript var inp document.querySelector(input); let isComposing false;function search() {if (isComposing) return false;console.log(搜索: , inp.valu…

(企業 / 公司項目) 企業項目如何使用jwt?

按照企業的項目然后寫的小demo&#xff0c; 自己搞一個登錄接口然后調用jwtUtil工具類 后端實現 創建一個通用模塊common來實現jwt生成token 登錄注冊的基本實現邏輯思路 面試| ProcessOn免費在線作圖,在線流程圖,在線思維導圖 注釋挺詳細的jwtUtil工具類&#xff0c; 封裝的…

WPF仿網易云搭建筆記(5):信息流控制之IOC容器

文章目錄 專欄和Gitee倉庫前言IOC容器Prism IOC使用聲明兩個測試的服務類MainWindow IOC 注入[單例]MainWindow里面獲取UserController無法使用官方解決方案 使用自定義IOC容器&#xff0c;完美解決既然Prism不好用&#xff0c;直接上微軟的IOC解決方案App.xaml.csViewModel里面…

網絡測試工具:tcping-測試端口連接

網絡測試工具&#xff1a;tcping-測試端口連接 平常使用的ping&#xff0c;是通過icmp協議去測試網絡連通性的&#xff0c;tcping是通過tcp三次握手測試端口的連通性。總的來說&#xff0c;ping測試的是L3的連通性&#xff0c;tcping測試的是L4的連通性。 tcping工具下載 htt…