java并發(fā)編程之進(jìn)程和線程調(diào)度基礎(chǔ)詳解
1.什么是進(jìn)程
進(jìn)程(process)是計算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。
在早期面向進(jìn)程設(shè)計的計算機(jī)結(jié)構(gòu)中,進(jìn)程是程序的基本執(zhí)行實(shí)體;在當(dāng)代面向線程設(shè)計的計算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。
——摘自百度百科
該怎么理解這句話呢,首先進(jìn)程和線程都是操作系統(tǒng)層面的概念,不是某種編程語言提出的。進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的最小單位,其中資源包括:CPU、內(nèi)存空間、磁盤IO等。進(jìn)程是程序在計算機(jī)上的一次執(zhí)行活動。顯然,程序是死的、靜態(tài)的,進(jìn)程是活的、動態(tài)的。
進(jìn)程可以分為系統(tǒng)進(jìn)程和用戶進(jìn)程,凡是用于完成操作系統(tǒng)的各種功能的進(jìn)程就是系統(tǒng)進(jìn)程,它們就是處于運(yùn)行狀態(tài)下的操作系統(tǒng)本身,用戶進(jìn)程就是所有由你啟動的進(jìn)程。
當(dāng)你運(yùn)行一個程序,你就啟動了一個進(jìn)程。例如我們使用java語言在windows系統(tǒng)中啟動一個main方法,就會系統(tǒng)就會創(chuàng)建一個名為java.exe的進(jìn)程。
windows系統(tǒng)中使用直接使用任務(wù)管理器查看進(jìn)程
windows系統(tǒng)中dos窗口鍵入 tasklist 命令查看進(jìn)程
linux系統(tǒng)中可以使用ps命令查看進(jìn)程
2.什么是線程
線程(thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。
它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。
一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。
線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計數(shù)器,一組寄存器和棧),但是它可與同屬一個進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源。
3.進(jìn)程和線程的區(qū)別與聯(lián)系
聯(lián)系:
- 進(jìn)程是線程的容器,一個進(jìn)程可以創(chuàng)建多個線程分支,兩者之間存在包含關(guān)系。
- 兩者都是多任務(wù)編程方式,都能使用計算機(jī)的多核資源。
- 線程具有進(jìn)程的許多特征,故又稱輕型進(jìn)程,傳統(tǒng)進(jìn)程稱重型進(jìn)程。
- 一個程序至少有一個進(jìn)程,一個進(jìn)程至少有一個線程。
- 線程是CPU調(diào)度的最小單位,必須依賴于進(jìn)程而存在
區(qū)別:
- 其實(shí)從概念上看的出,進(jìn)程主要用于操作系統(tǒng)資源分配,線程主要在于CPU運(yùn)算調(diào)度
- 進(jìn)程和進(jìn)程之間是相互獨(dú)立的,每個進(jìn)程有獨(dú)立的地址空間,一個進(jìn)程崩潰后,在保護(hù)模式下不會對其它進(jìn)程產(chǎn)生影響。而同一個進(jìn)程下的多個線程共享資源。
- 線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。
- 在執(zhí)行過程中,每個獨(dú)立的線程有一個程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
- 相對進(jìn)程而言,線程是一個更加接近于執(zhí)行體的概念,它可以與同進(jìn)程中的其他線程共享數(shù)據(jù),但擁有自己的??臻g,擁有獨(dú)立的執(zhí)行序列。
4.CPU內(nèi)核數(shù)和線程數(shù)的關(guān)系
通常我們在選購電腦的時候,CPU是一個需要考慮到核心因素,因?yàn)樗鼪Q定了電腦的性能等級。
CPU從早期的單核,發(fā)展到現(xiàn)在的雙核,多核。在介紹CPU屬性時經(jīng)常會說“幾核幾線程”。
那么這個參數(shù)到底代表什么意思呢?
“幾核”,就是指CPU核心數(shù),“幾線程”,就是線程數(shù),也稱邏輯CPU個數(shù)。
CPU的核心數(shù) 是指物理上,也就是硬件上存在著幾個核心。比如,雙核就是包括2個相對獨(dú)立的CPU核心單元組,四核就包含4個相對獨(dú)立的CPU核心單元組。
線程數(shù) 表示該CPU能同一時刻能夠執(zhí)行的最大線程數(shù)量 也稱邏輯CPU數(shù) 是一種邏輯的概念,簡單地說,就是模擬出的CPU核心數(shù)。一個核心最少對應(yīng)一個線程,但引入超線程技術(shù)后一個核心可以對應(yīng)兩個線程,也就是說它可以同時運(yùn)行兩個線程。
對于win7操作系統(tǒng)來說
我們從任務(wù)管理器的性能標(biāo)簽頁,以及設(shè)備管理器中處理器看到的就是線程數(shù)。
DOS窗口中輸入“wmic”
然后在出現(xiàn)的新窗口中輸入“cpu get * ”也可查看物理CPU數(shù)、CPU核心數(shù)、線程數(shù)。
- Name:物理CPU名稱
- NumberOfCores:表示CPU核心數(shù)
- NumberOfLogicalProcessors:表示CPU線程數(shù)
所以我這臺win7主機(jī)CPU配置的就是 2核心 4線程
而對于win10系統(tǒng)來說,任務(wù)管理器中已經(jīng)標(biāo)明了線程數(shù)。
在Linux系統(tǒng)中,可以根據(jù)下面命令查看
查詢CPU個數(shù):
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
查詢核心數(shù):
cat /proc/cpuinfo| grep "cpu cores"| uniq
查詢邏輯CPU總數(shù)(線程數(shù)):
cat /proc/cpuinfo| grep "processor"| wc -l
當(dāng)然我們java也可以查看CPU線程數(shù)
public static void main(String[] args) { int i = Runtime.getRuntime().availableProcessors(); System.out.println("當(dāng)前主機(jī)CPU線程數(shù):"+i); }
5.CPU時間片輪轉(zhuǎn)調(diào)度機(jī)制
不知道大家有沒有疑問,一般高配置服務(wù)器的CPU 核心數(shù)也僅僅有32核,使用超線程技術(shù)同一時刻能執(zhí)行的最大線程數(shù)也就是64,而服務(wù)器卻能承載一秒10w多的訪問量。而且我們平時在開發(fā)的時候,感覺并沒有受 CPU 核心數(shù)的限制,想啟動線程就啟動線程,哪怕是在單核 CPU 上,為什么?
其實(shí)首先我們得明確兩個概念,一個是并行 一個是并發(fā)
并行 是指兩個或者多個事件在同一時刻發(fā)生。
并發(fā) 是指兩個或多個事件在同一時間間隔發(fā)生,當(dāng)談?wù)摬l(fā)的時候一定要加個單位時間,也就是說單位時間內(nèi)并發(fā)量是多少?離開了單位時間其實(shí)是沒有意義的。
所以可以說,上面那個服務(wù)器 并行數(shù)是64,而每秒能承載的并發(fā)量是10w
那么CPU怎么做到每秒執(zhí)行10w線程數(shù)的呢,這是由于操作系統(tǒng)提供了一種CPU時間片輪轉(zhuǎn)調(diào)度機(jī)制。
操作系統(tǒng)一般是按照一定策略,定期給每個活動的進(jìn)程執(zhí)行其內(nèi)部程序的機(jī)會,并且每次只執(zhí)行一小段時間,然后操作系統(tǒng)利用中斷強(qiáng)行退出執(zhí)行,將當(dāng)前程序信息壓棧,然后開始執(zhí)行下一個進(jìn)程的一小段程序。通過這樣不斷快速的循環(huán)切換,每個程序都獲得執(zhí)行,在用戶看來,感覺到很多程序都在平行的執(zhí)行。
當(dāng)然在自己的程序運(yùn)行時不是獨(dú)一無二的,我們看似很順暢的工作,其實(shí)是由一個個的執(zhí)行片段構(gòu)成的,我們眼中相鄰的兩條語句甚至同一個語句中兩個不同的運(yùn)算符之間,都有可能插入其他線程或進(jìn)程的動作。
時間片輪轉(zhuǎn)調(diào)度是一種最古老、最簡單、最公平且使用最廣的算法,又稱RR(Round-Robin)調(diào)度。
每個進(jìn)程被分配一個時間段,稱作它的時間片,即該進(jìn)程允許運(yùn)行的時間。
百度百科對CPU時間片輪轉(zhuǎn)機(jī)制原理解釋如下:
如果在時間片結(jié)束時進(jìn)程還在運(yùn)行,則CPU將被剝奪并分配給另一個進(jìn)程。
如果進(jìn)程在時間片結(jié)束前阻塞或結(jié)束,則CPU當(dāng)即進(jìn)行切換。
調(diào)度程序所要做的就是維護(hù)一張就緒進(jìn)程列表,當(dāng)進(jìn)程用完它的時間片后,它被移到隊(duì)列的末尾。
時間片輪轉(zhuǎn)算法的基本思想是,系統(tǒng)將所有的就緒進(jìn)程按先來先服務(wù)算法的原則,排成一個隊(duì)列,每次調(diào)度時,系統(tǒng)把處理機(jī)分配給隊(duì)列首進(jìn)程,并讓其執(zhí)行一個時間片。
當(dāng)執(zhí)行的時間片用完時,由一個計時器發(fā)出時鐘中斷請求,調(diào)度程序根據(jù)這個請求停止該進(jìn)程的運(yùn)行,將它送到就緒隊(duì)列的末尾,再把處理機(jī)分給就緒隊(duì)列中新的隊(duì)列首進(jìn)程,同時讓它也執(zhí)行一個時間片。
6.上下文切換
而這種時間片輪轉(zhuǎn)是有代價的,往往還會伴隨著上下文切換。
任何對進(jìn)程或者線程的調(diào)度,都會引入額外的開銷,這個開銷中就包括上下文切換(Context Switch),有時也稱做進(jìn)程切換或任務(wù)切換,是指CPU 從一個進(jìn)程或線程切換到另一個進(jìn)程或線程。
在上下文切換過程中,CPU會停止處理當(dāng)前運(yùn)行的程序,并保存當(dāng)前程序運(yùn)行的具體位置以便之后繼續(xù)運(yùn)行。從這個角度來看,上下文切換有點(diǎn)像我們同時閱讀幾本書,在來回切換書本的同時我們需要記住每本書當(dāng)前讀到的頁碼。
例如我們經(jīng)常用做緩存的redis,采用的就是單線程的方式,大大減少上下文切換產(chǎn)生的額外開銷。
上下文切換通常是計算密集型的。也就是說上下文切換對系統(tǒng)來說意味著消耗大量的 CPU 時間,事實(shí)上,可能是操作系統(tǒng)中時間消耗最大的操作。
根據(jù)Tsuna的測試報告,每次上下文切換都需要幾十納秒到數(shù)微妙的CPU時間。這個時間還是相當(dāng)可觀的,特別是上下文切換次數(shù)較多的情況下,很容易導(dǎo)致CPU將大量的時間耗費(fèi)在寄存器、內(nèi)核棧、以及虛擬內(nèi)存等資源的保存和恢復(fù)上,進(jìn)而大大縮短了真正運(yùn)行的時間。
引起線程上下文切換的原因大概有以下幾種:
- 當(dāng)前執(zhí)行任務(wù)的時間片用完之后,系統(tǒng)CPU正常調(diào)度下一個任務(wù)。
- 當(dāng)前執(zhí)行任務(wù)碰到IO阻塞,調(diào)度器將此任務(wù)掛起,繼續(xù)下一任務(wù)。
- 多個任務(wù)搶占鎖資源,當(dāng)前任務(wù)沒有搶到鎖資源,被調(diào)度器掛起,繼續(xù)下一任務(wù)。
- 用戶代碼掛起當(dāng)前任務(wù),讓出CPU時間。
7.并發(fā)編程的意義、好處
由于多核多線程的CPU的誕生,多線程、高并發(fā)的編程越來越受重視和關(guān)注。多線程可以給程序帶來如下好處。
(1)充分利用CPU的資源
從上面的CPU的介紹,可以看的出來,現(xiàn)在市面上沒有CPU的內(nèi)核不使用多線程并發(fā)機(jī)制的,特別是服務(wù)器還不止一個CPU,如果還是使用單線程的技術(shù)做思路,明顯就out了。因?yàn)槌绦虻幕菊{(diào)度單元是線程,并且一個線程也只能在一個CPU的一個核的一個線程跑,如果你是個i3的CPU的話,最差也是雙核心4線程的運(yùn)算能力:如果是一個線程的程序的話,那是要浪費(fèi)3/4的CPU性能:如果設(shè)計一個多線程的程序的話,那它就可以同時在多個CPU的多個核的多個線程上跑,可以充分地利用CPU,減少CPU的空閑時間,發(fā)揮它的運(yùn)算能力,提高并發(fā)量。
就像我們平時坐地鐵一樣,很多人坐長線地鐵的時候都在認(rèn)真看書,而不是到家了再去看書,這樣你的時間就相當(dāng)于有了兩倍。這就是為什么有些人時間很充裕,而有些人老是說沒時間的一個原因,工作也是這樣,有的時候可以并發(fā)地去做幾件事情,充分利用我們的時間,CPU也是一樣,也要充分利用。
(2)加快響應(yīng)用戶的時間
比如我們經(jīng)常用的迅雷下載,都喜歡多開幾個線程去下載,誰都不愿意用一個線程去下載,為什么呢?答案很簡單,就是多個線程下載快啊。
(3)可以使你的代碼模塊化,異步化,簡單化
例如我們實(shí)現(xiàn)電商系統(tǒng),下訂單和給用戶發(fā)送短信、郵件就可以進(jìn)行拆分,將給用戶發(fā)送短信、郵件這兩個步驟獨(dú)立為單獨(dú)的模塊,并交給其他線程去執(zhí)行。這樣既增加了異步的操作,提升了系統(tǒng)性能,又使程序模塊化,清晰化和簡單化。
多線程應(yīng)用開發(fā)的好處還有很多,大家在日后的代碼編寫過程中可以慢慢體會它的魅力。
8.并發(fā)編程需要注意事項(xiàng)
1.線程之間的安全性
從前面的章節(jié)中我們都知道,在同一個進(jìn)程里面的多線程是資源共享的,也就是都可以訪問同一個內(nèi)存地址當(dāng)中的一個變量。
例如:若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的:若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
2.線程之間的死鎖
為了解決線程之間的安全性引入了Java的鎖機(jī)制,而一不小心就會產(chǎn)生Java線程死鎖的多線程問題,因?yàn)椴煌木€程都在等待那些根本不可能被釋放的鎖,從而導(dǎo)致所有的工作都無法完成。假設(shè)有兩個線程,分別代表兩個饑餓的人,他們必須共享刀叉并輪流吃飯。他們都需要獲得兩個鎖:共享刀和共享叉的鎖。
假如線程A獲得了刀,而線程B獲得了叉。線程A就會進(jìn)入阻塞狀態(tài)來等待獲得叉,而線程B則阻塞來等待線程A所擁有的刀。這只是人為設(shè)計的例子,但盡管在運(yùn)行時很難探測到,這類情況卻時常發(fā)生
3.線程太多了會將服務(wù)器資源耗盡形成死機(jī)宕機(jī)
線程數(shù)太多有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及CPU的“過渡切換”,造成系統(tǒng)的死機(jī),那么我們該如何解決這類問題呢?
某些系統(tǒng)資源是有限的,如文件描述符。多線程程序可能耗盡資源,因?yàn)槊總€線程都可能希望有一個這樣的資源。如果線程數(shù)相當(dāng)大,或者某個資源的侯選線程數(shù)遠(yuǎn)遠(yuǎn)超過了可用的資源數(shù)則最好使用資源池。一個最好的示例是數(shù)據(jù)庫連接池。只要線程需要使用一個數(shù)據(jù)庫連接,它就從池中取出一個,使用以后再將它返回池中。資源池也稱為資源庫。
多線程應(yīng)用開發(fā)的注意事項(xiàng)很多,希望大家在日后的工作中可以慢慢體會它的危險所在。
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring boot @PathVariable傳遞帶反斜杠參數(shù) / 的處理
這篇文章主要介紹了spring boot @PathVariable傳遞帶反斜杠參數(shù) / 的處理操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02濫用@PathVariable導(dǎo)致bug原因分析解決
這篇文章主要為大家介紹了濫用@PathVariable導(dǎo)致bug原因分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Spring?@bean和@component注解區(qū)別
本文主要介紹了Spring?@bean和@component注解區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01SpringSecurity JWT基于令牌的無狀態(tài)認(rèn)證實(shí)現(xiàn)
Spring Security中實(shí)現(xiàn)基于JWT的無狀態(tài)認(rèn)證是一種常見的做法,本文就來介紹一下SpringSecurity JWT基于令牌的無狀態(tài)認(rèn)證實(shí)現(xiàn),感興趣的可以了解一下2025-04-04Java Socket編程實(shí)例(四)- NIO TCP實(shí)踐
這篇文章主要講解Java Socket編程中NIO TCP的實(shí)例,希望能給大家做一個參考。2016-06-06Java中JSONObject與JSONArray的使用區(qū)別詳解
這篇文章主要介紹了Java中JSONObject與JSONArray的使用區(qū)別詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11Springboot優(yōu)化內(nèi)置服務(wù)器Tomcat優(yōu)化方式(underTow)
本文詳細(xì)介紹了Spring Boot中Tomcat和Undertow服務(wù)器的配置和優(yōu)化,包括初始線程數(shù)、最大線程數(shù)、最小備用線程數(shù)、最大請求數(shù)等參數(shù)的優(yōu)化建議,以及在高并發(fā)場景下Undertow相對于Tomcat的優(yōu)勢2024-12-12