Java 并发总结——进程与线程
一、進(jìn)程與線程
(1)線程與進(jìn)程
? ? 進(jìn)程是程序在一個(gè)數(shù)據(jù)集合上運(yùn)行的過程,它是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。進(jìn)程實(shí)體由程序段, 數(shù)據(jù)段 PCB(進(jìn)程控制塊)組成。
? ?線程可以看做輕量級(jí)進(jìn)程,線程是進(jìn)程的執(zhí)行單元,是進(jìn)程調(diào)度的基本單位。
(2)多線程中的上下文切換
在上下文切換過程中,CPU 會(huì)停止處理當(dāng)前運(yùn)行的程序,并保存當(dāng)前程序運(yùn)行的具體位置以便之后繼續(xù)運(yùn)行。在程序中,上下文切換過程中的“頁碼”信息是保存在進(jìn)程控制塊(PCB)中的。PCB 還經(jīng)常被稱作“切換楨”(switchframe)。“頁碼”信息會(huì)一直保存到 CPU 的內(nèi)存中,直到他們被再次使用。
上下文切換是存儲(chǔ)和恢復(fù) CPU 狀態(tài)的過程,它使得線程執(zhí)行能夠從中斷點(diǎn)恢復(fù)執(zhí)行。上下文切換是多任務(wù)操作系統(tǒng)和多線程環(huán)境的基本特征。
(3)多線程同步和互斥實(shí)現(xiàn)方法
1、線程同步
指線程之間所具有的一種制約關(guān)系,一個(gè)線程的執(zhí)行依賴另一個(gè)線程的消息,當(dāng)它沒有得到另一個(gè)線程的消息時(shí)應(yīng)等待,直到消息到達(dá)時(shí)才被喚醒。
2、線程互斥
指對(duì)于共享的進(jìn)程系統(tǒng)資源,在各單個(gè)線程訪問時(shí)的排它性。當(dāng)有若干個(gè)線程都要使用某一共享資源時(shí),任何時(shí)刻最多只允許一個(gè)線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步。
線程間的同步方法大體可分為兩類:用戶模式和內(nèi)核模式。
內(nèi)核模式:指利用系統(tǒng)內(nèi)核對(duì)象的單一性來進(jìn)行同步,使用時(shí)需要切換內(nèi)核態(tài)與用戶態(tài),
用戶模式:就是不需要切換到內(nèi)核態(tài),只在用戶態(tài)完成操作。
用戶模式下的方法有:原子操作(例如一個(gè)單一的全局變量),臨界區(qū)。
內(nèi)核模式下的方法有:事件,信號(hào)量,互斥量。
(3)進(jìn)程調(diào)度算法
1、實(shí)時(shí)系統(tǒng)
FIFO(First Input First Output,先進(jìn)先出算法)
SJF(Shortest Job First,最短作業(yè)優(yōu)先算法)
SRTF(Shortest Remaining Time First,最短剩余時(shí)間優(yōu)先算法)。
2、交互式系統(tǒng)
RR(Round Robin,時(shí)間片輪轉(zhuǎn)算法)
HPF(Highest Priority First,最高優(yōu)先級(jí)算法)
多級(jí)隊(duì)列
最短進(jìn)程優(yōu)先
保證調(diào)度,彩票調(diào)度,公平分享調(diào)度。
二、Java線程
(1)創(chuàng)建線程的方式
1、繼承 Thread 類創(chuàng)建線程類
2、通過 Runnable 接口創(chuàng)建線程類
3、通過 Callable 和 Future 創(chuàng)建線程
4、通過線程池創(chuàng)建
(2)?守護(hù)線程和本地線程
在Java中有兩類線程:用戶線程 (User Thread)、守護(hù)線程 (Daemon Thread、gc線程、Finalizer線程)
守護(hù)線程和用戶線程的區(qū)別在于:守護(hù)線程依賴于創(chuàng)建它的線程,而用戶線程則不依賴。
擴(kuò)展:
Thread Dump 打印出來的線程信息,含有 daemon 字樣的線程即為守護(hù)
進(jìn)程,可能會(huì)有:服務(wù)守護(hù)進(jìn)程、編譯守護(hù)進(jìn)程、windows 下的監(jiān)聽 Ctrl+break
的守護(hù)進(jìn)程、Finalizer 守護(hù)進(jìn)程、引用處理守護(hù)進(jìn)程、GC 守護(hù)進(jìn)程。
(3)Java線程的生命周期
新建(New):創(chuàng)建后尚位啟動(dòng)的線程的狀態(tài)
運(yùn)行(Runnable):包含Running和Ready
無限期等待(Waiting):不會(huì)被分配CPU執(zhí)行時(shí)間,需要顯式唄喚醒
限期等待(Timed Waiting):在一定時(shí)間后會(huì)由系統(tǒng)自動(dòng)喚醒
阻塞(Blocked):等待排它鎖
結(jié)束(Terminated):已終止線程的狀態(tài),線程已經(jīng)結(jié)束執(zhí)行
(4)線程調(diào)度的方法
1、wait()
使一個(gè)線程處于等待(阻塞)狀態(tài),并且釋放所持有的對(duì)象的鎖;
2、sleep()
使一個(gè)正在運(yùn)行的線程處于睡眠狀態(tài),是一個(gè)靜態(tài)方法,調(diào)用此方法要處理InterruptedException異常;
3、notify()
喚醒一個(gè)處于等待狀態(tài)的線程,當(dāng)然在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程,而是由JVM確定喚醒哪個(gè)線程,而且與優(yōu)先級(jí)無關(guān);
4、notityAll()
喚醒所有處于等待狀態(tài)的線程,該方法并不是將對(duì)象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進(jìn)入就緒狀態(tài);
(5)線程調(diào)度算法
有兩種調(diào)度模型:分時(shí)調(diào)度模型和搶占式調(diào)度模型。
分時(shí)調(diào)度模型是指讓所有的線程輪流獲得 cpu 的使用權(quán),并且平均分配每個(gè)線程占 用的 CPU 的時(shí)間片。搶占式調(diào)度模型是指優(yōu)先讓可運(yùn)行池中優(yōu)先級(jí)高的線程占用
CPU,如果可運(yùn)行池中的線程優(yōu)先級(jí)相同,那么就隨機(jī)選擇一個(gè)線程,使其占用
CPU。處于運(yùn)行狀態(tài)的線程會(huì)一直運(yùn)行,直至它不得不放棄 CPU。
java虛擬機(jī)采用時(shí)間片輪轉(zhuǎn)的方式。可以設(shè)置線程的優(yōu)先級(jí),會(huì)映射到下層的系統(tǒng)上面的優(yōu)
先級(jí)上,如非特別需要,盡量不要用,防止線程饑餓。
三、死鎖
(1)死鎖產(chǎn)生的4個(gè)必要條件
1、互斥條件
2、請(qǐng)求和保持條件
3、不可搶占條件
4)循環(huán)等待條件
(2)死鎖類型
1、鎖順序死鎖(交錯(cuò)執(zhí)行)
2、動(dòng)態(tài)鎖順序死鎖
3、協(xié)作對(duì)象之間發(fā)生死鎖(隱式獲取兩個(gè)鎖)
(3)避免死鎖的方法
1、固定加鎖的順序(針對(duì)鎖順序死鎖)
2、開放調(diào)用(針對(duì)對(duì)象之間協(xié)作造成的死鎖)
3、使用定時(shí)鎖-->tryLock()
4、如果等待獲取鎖時(shí)間超時(shí),則拋出異常而不是一直等待!
(4)死鎖檢測
JDK提供了兩種方式檢測:
JconsoleJDK自帶的圖形化界面工具,使用JDK給我們的的工具JConsole
Jstack是JDK自帶的命令行工具,主要用于線程Dump分析。
1、Jstack命令
jstack是java虛擬機(jī)自帶的一種堆棧跟蹤工具。jstack用于打印出給定的java進(jìn)程ID或core file或遠(yuǎn)程調(diào)試服務(wù)的Java堆棧信息。 Jstack工具可以用于生成java虛擬機(jī)當(dāng)前時(shí)刻的線程快照。線程快照是當(dāng)前java虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長時(shí)間停頓的原因,如線程間死鎖、死循環(huán)、請(qǐng)求外部資源導(dǎo)致的長時(shí)間等待等。 線程出現(xiàn)停頓的時(shí)候通過jstack來查看各個(gè)線程的調(diào)用堆棧,就可以知道沒有響應(yīng)的線程到底在后臺(tái)做什么事情,或者等待什么資源。
2、JConsole工具
Jconsole是JDK自帶的監(jiān)控工具,在JDK/bin目錄下可以找到。它用于連接正在運(yùn)行的本地或者遠(yuǎn)程的JVM,對(duì)運(yùn)行在Java應(yīng)用程序的資源消耗和性能進(jìn)行監(jiān)控,并畫出大量的圖表,提供強(qiáng)大的可視化界面。而且本身占用的服務(wù)器內(nèi)存很小,甚至可以說幾乎不消耗。
(5)死鎖、活鎖、饑餓
1、死鎖
是指兩個(gè)或兩個(gè)以上的進(jìn)程(或線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。
2、活鎖
任務(wù)或者執(zhí)行者沒有被阻塞,由于某些條件沒有滿足,導(dǎo)致一直重復(fù)嘗試,
失敗,嘗試,失敗。
活鎖和死鎖的區(qū)別在于,處于活鎖的實(shí)體是在不斷的改變狀態(tài),所謂的“活”, 而
處于死鎖的實(shí)體表現(xiàn)為等待;活鎖有可能自行解開,死鎖則不能。
3、饑餓
一個(gè)或者多個(gè)線程因?yàn)榉N種原因無法獲得所需要的資源,導(dǎo)致一直無法執(zhí)
行的狀態(tài)。
Java 中導(dǎo)致饑餓的原因:
1)高優(yōu)先級(jí)線程吞噬所有的低優(yōu)先級(jí)線程的 CPU 時(shí)間。
2)線程被永久堵塞在一個(gè)等待進(jìn)入同步塊的狀態(tài),因?yàn)槠渌€程總是能在它之前
持續(xù)地對(duì)該同步塊進(jìn)行訪問。
3)線程在等待一個(gè)本身也處于永久等待完成的對(duì)象(比如調(diào)用這個(gè)對(duì)象的 wait 方 法),因?yàn)槠渌€程總是被持續(xù)地獲得喚醒。
四、擴(kuò)展
(1)避免死鎖
預(yù)防死鎖的幾種策略,會(huì)嚴(yán)重地?fù)p害系統(tǒng)性能。因此在避免死鎖時(shí),要施加較弱的限制,從而獲得 較滿意的系統(tǒng)性能。由于在避免死鎖的策略中,允許進(jìn)程動(dòng)態(tài)地申請(qǐng)資源。因而,系統(tǒng)在進(jìn)行資源分配之前預(yù)先計(jì)算資源分配的安全性。若此次分配不會(huì)導(dǎo)致系統(tǒng)進(jìn)入不安全的狀態(tài),則將資源分配給進(jìn)程;否則,進(jìn)程等待。其中最具有代表性的避免死鎖算法是銀行家算法。
銀行家算法:首先需要定義狀態(tài)和安全狀態(tài)的概念。系統(tǒng)的狀態(tài)是當(dāng)前給進(jìn)程分配的資源情況。因此,狀態(tài)包含兩個(gè)向量Resource(系統(tǒng)中每種資源的總量)和Available(未分配給進(jìn)程的每種資源的總量)及兩個(gè)矩陣Claim(表示進(jìn)程對(duì)資源的需求)和Allocation(表示當(dāng)前分配給進(jìn)程的資源)。安全狀態(tài)是指至少有一個(gè)資源分配序列不會(huì)導(dǎo)致死鎖。當(dāng)進(jìn)程請(qǐng)求一組資源時(shí),假設(shè)同意該請(qǐng)求,從而改變了系統(tǒng)的狀態(tài),然后確定其結(jié)果是否還處于安全狀態(tài)。如果是,同意這個(gè)請(qǐng)求;如果不是,阻塞該進(jìn)程知道同意該請(qǐng)求后系統(tǒng)狀態(tài)仍然是安全的。
(2)檢測死鎖
首先為每個(gè)進(jìn)程和每個(gè)資源指定一個(gè)唯一的號(hào)碼;
然后建立資源分配表和進(jìn)程等待表。
(3)解除死鎖
當(dāng)發(fā)現(xiàn)有進(jìn)程死鎖后,便應(yīng)立即把它從死鎖狀態(tài)中解脫出來,常采用的方法有:
奪資源:從其它進(jìn)程剝奪足夠數(shù)量的資源給死鎖進(jìn)程,以解除死鎖狀態(tài);
撤消進(jìn)程:可以直接撤消死鎖進(jìn)程或撤消代價(jià)最小的進(jìn)程,直至有足夠的資源可用,死鎖狀態(tài).消除為止;所謂代價(jià)是指優(yōu)先級(jí)、運(yùn)行代價(jià)、進(jìn)程的重要性和價(jià)值等。
五、問題
(1)進(jìn)程與線程的區(qū)別
進(jìn)程是資源分配的最小單位
線程是CPU調(diào)度的最小單位
一個(gè)程序必須有一個(gè)進(jìn)程,一個(gè)進(jìn)程必須有一個(gè)線程
(2)Thread中start和run方法的區(qū)別
調(diào)用start()方法會(huì)創(chuàng)建一個(gè)新的子線程并啟動(dòng)
run()方法只是Thread的一個(gè)普通方法的調(diào)用
(3)Thread和Runnable的關(guān)系
Thread是實(shí)現(xiàn)了Runnable接口的類,使得run支持多線程
因類的單一繼承原則,推薦多使用Runnable接口
(4)wait()和sleep()的區(qū)別
sleep是Thread類的方法,wait是Object類中定義的方法
sleep()方法可以在任何地方使用
wait()方法只能在sychronized方法或synchronized塊中使用
Thread.sleep只會(huì)讓出CPU,不會(huì)導(dǎo)致鎖行為的改變
Object.wait不僅讓出CPU,還會(huì)釋放已經(jīng)占有的同步資源鎖
(5)鎖池與等待池
在java中,每個(gè)對(duì)象都有兩個(gè)池,鎖(monitor)池和等待池
wait() ,notifyAll(),notify() 三個(gè)方法都是Object類中的方法.
鎖池:假設(shè)線程A已經(jīng)擁有了某個(gè)對(duì)象(注意:不是類)的鎖,而其它的線程想要調(diào)用這個(gè)對(duì)象的某個(gè)synchronized方法(或者synchronized塊),由于這些線程在進(jìn)入對(duì)象的synchronized方法之前必須先獲得該對(duì)象的鎖的擁有權(quán),但是該對(duì)象的鎖目前正被線程A擁有,所以這些線程就進(jìn)入了該對(duì)象的鎖池中。
等待池:假設(shè)一個(gè)線程A調(diào)用了某個(gè)對(duì)象的wait()方法,線程A就會(huì)釋放該對(duì)象的鎖(因?yàn)閣ait()方法必須出現(xiàn)在synchronized中,這樣自然在執(zhí)行wait()方法之前線程A就已經(jīng)擁有了該對(duì)象的鎖),同時(shí)線程A就進(jìn)入到了該對(duì)象的等待池中。如果另外的一個(gè)線程調(diào)用了相同對(duì)象的notifyAll()方法,那么處于該對(duì)象的等待池中的線程就會(huì)全部進(jìn)入該對(duì)象的鎖池中,準(zhǔn)備爭奪鎖的擁有權(quán)。如果另外的一個(gè)線程調(diào)用了相同對(duì)象的notify()方法,那么僅僅有一個(gè)處于該對(duì)象的等待池中的線程(隨機(jī))會(huì)進(jìn)入該對(duì)象的鎖池.
(6)notify和notifyAll的區(qū)別
notifyAll:會(huì)讓所有處于等待池的線程全部進(jìn)入鎖池去競爭獲取鎖的機(jī)會(huì)
notify:會(huì)隨機(jī)讓一個(gè)處于等待池中的線程進(jìn)入鎖池去競爭獲取鎖的機(jī)會(huì)
(7)interrupt函數(shù)
通知線程應(yīng)該中斷了
- 如果線程處于阻塞狀態(tài),那么線程將立即退出被阻塞狀態(tài),拋出一個(gè)InterruptedException異常
- 如果線程處于正常活動(dòng)狀態(tài),那么會(huì)將該線程的中斷標(biāo)志設(shè)置為true。被設(shè)置中斷標(biāo)志的線程將繼續(xù)正常運(yùn)行,不受影響
(8)線程狀態(tài)以及狀態(tài)之間的轉(zhuǎn)換
總結(jié)
以上是生活随笔為你收集整理的Java 并发总结——进程与线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 张丰毅演过哪些电视剧 这四部您都看过那部
- 下一篇: Java 并发总结——AQS