Java避免死鎖_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
在有些情況下死鎖是可以避免的。本文將展示三種用于避免死鎖的技術(shù):
1.加鎖順序
2.加鎖時(shí)限
3.死鎖檢測(cè)
加鎖順序
當(dāng)多個(gè)線(xiàn)程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發(fā)生。
如果能確保所有的線(xiàn)程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生??聪旅孢@個(gè)例子:
Thread 1: lock A lock B Thread 2: wait for A lock C (when A locked) Thread 3: wait for A wait for B wait for C
如果一個(gè)線(xiàn)程(比如線(xiàn)程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。
例如,線(xiàn)程2和線(xiàn)程3只有在獲取了鎖A之后才能?chē)L試獲取鎖C(獲取鎖A是獲取鎖C的必要條件)。因?yàn)榫€(xiàn)程1已經(jīng)擁有了鎖A,所以線(xiàn)程2和3需要一直等到鎖A被釋放。然后在它們嘗試對(duì)B或C加鎖之前,必須成功地對(duì)A加了鎖。
按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。但是,這種方式需要你事先知道所有可能會(huì)用到的鎖(并對(duì)這些鎖做適當(dāng)?shù)呐判?,但總有些時(shí)候是無(wú)法預(yù)知的。
加鎖時(shí)限
另外一個(gè)可以避免死鎖的方法是在嘗試獲取鎖的時(shí)候加一個(gè)超時(shí)時(shí)間,這也就意味著在嘗試獲取鎖的過(guò)程中若超過(guò)了這個(gè)時(shí)限該線(xiàn)程則放棄對(duì)該鎖請(qǐng)求。若一個(gè)線(xiàn)程沒(méi)有在給定的時(shí)限內(nèi)成功獲得所有需要的鎖,則會(huì)進(jìn)行回退并釋放所有已經(jīng)獲得的鎖,然后等待一段隨機(jī)的時(shí)間再重試。這段隨機(jī)的等待時(shí)間讓其它線(xiàn)程有機(jī)會(huì)嘗試獲取相同的這些鎖,并且讓該應(yīng)用在沒(méi)有獲得鎖的時(shí)候可以繼續(xù)運(yùn)行(加鎖超時(shí)后可以先繼續(xù)運(yùn)行干點(diǎn)其它事情,再回頭來(lái)重復(fù)之前加鎖的邏輯)。
以下是一個(gè)例子,展示了兩個(gè)線(xiàn)程以不同的順序嘗試獲取相同的兩個(gè)鎖,在發(fā)生超時(shí)后回退并重試的場(chǎng)景:
Thread 1 locks A Thread 2 locks B Thread 1 attempts to lock B but is blocked Thread 2 attempts to lock A but is blocked Thread 1's lock attempt on B times out Thread 1 backs up and releases A as well Thread 1 waits randomly (e.g. 257 millis) before retrying. Thread 2's lock attempt on A times out Thread 2 backs up and releases B as well Thread 2 waits randomly (e.g. 43 millis) before retrying.
在上面的例子中,線(xiàn)程2比線(xiàn)程1早200毫秒進(jìn)行重試加鎖,因此它可以先成功地獲取到兩個(gè)鎖。這時(shí),線(xiàn)程1嘗試獲取鎖A并且處于等待狀態(tài)。當(dāng)線(xiàn)程2結(jié)束時(shí),線(xiàn)程1也可以順利的獲得這兩個(gè)鎖(除非線(xiàn)程2或者其它線(xiàn)程在線(xiàn)程1成功獲得兩個(gè)鎖之前又獲得其中的一些鎖)。
需要注意的是,由于存在鎖的超時(shí),所以我們不能認(rèn)為這種場(chǎng)景就一定是出現(xiàn)了死鎖。也可能是因?yàn)楂@得了鎖的線(xiàn)程(導(dǎo)致其它線(xiàn)程超時(shí))需要很長(zhǎng)的時(shí)間去完成它的任務(wù)。
此外,如果有非常多的線(xiàn)程同一時(shí)間去競(jìng)爭(zhēng)同一批資源,就算有超時(shí)和回退機(jī)制,還是可能會(huì)導(dǎo)致這些線(xiàn)程重復(fù)地嘗試但卻始終得不到鎖。如果只有兩個(gè)線(xiàn)程,并且重試的超時(shí)時(shí)間設(shè)定為0到500毫秒之間,這種現(xiàn)象可能不會(huì)發(fā)生,但是如果是10個(gè)或20個(gè)線(xiàn)程情況就不同了。因?yàn)檫@些線(xiàn)程等待相等的重試時(shí)間的概率就高的多(或者非常接近以至于會(huì)出現(xiàn)問(wèn)題)。 (超時(shí)和重試機(jī)制是為了避免在同一時(shí)間出現(xiàn)的競(jìng)爭(zhēng),但是當(dāng)線(xiàn)程很多時(shí),其中兩個(gè)或多個(gè)線(xiàn)程的超時(shí)時(shí)間一樣或者接近的可能性就會(huì)很大,因此就算出現(xiàn)競(jìng)爭(zhēng)而導(dǎo)致超時(shí)后,由于超時(shí)時(shí)間一樣,它們又會(huì)同時(shí)開(kāi)始重試,導(dǎo)致新一輪的競(jìng)爭(zhēng),帶來(lái)了新的問(wèn)題。)
這種機(jī)制存在一個(gè)問(wèn)題,在Java中不能對(duì)synchronized同步塊設(shè)置超時(shí)時(shí)間。你需要?jiǎng)?chuàng)建一個(gè)自定義鎖,或使用Java5中java.util.concurrent包下的工具。寫(xiě)一個(gè)自定義鎖類(lèi)不復(fù)雜,但超出了本文的內(nèi)容。
死鎖檢測(cè)
死鎖檢測(cè)是一個(gè)更好的死鎖預(yù)防機(jī)制,它主要是針對(duì)那些不可能實(shí)現(xiàn)按序加鎖并且鎖超時(shí)也不可行的場(chǎng)景。
每當(dāng)一個(gè)線(xiàn)程獲得了鎖,會(huì)在線(xiàn)程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map、graph等等)將其記下。除此之外,每當(dāng)有線(xiàn)程請(qǐng)求鎖,也需要記錄在這個(gè)數(shù)據(jù)結(jié)構(gòu)中。
當(dāng)一個(gè)線(xiàn)程請(qǐng)求鎖失敗時(shí),這個(gè)線(xiàn)程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生。例如,線(xiàn)程A請(qǐng)求鎖7,但是鎖7這個(gè)時(shí)候被線(xiàn)程B持有,這時(shí)線(xiàn)程A就可以檢查一下線(xiàn)程B是否已經(jīng)請(qǐng)求了線(xiàn)程A當(dāng)前所持有的鎖。如果線(xiàn)程B確實(shí)有這樣的請(qǐng)求,那么就是發(fā)生了死鎖(線(xiàn)程A擁有鎖1,請(qǐng)求鎖7;線(xiàn)程B擁有鎖7,請(qǐng)求鎖1)。
當(dāng)然,死鎖一般要比兩個(gè)線(xiàn)程互相持有對(duì)方的鎖這種情況要復(fù)雜的多。線(xiàn)程A等待線(xiàn)程B,線(xiàn)程B等待線(xiàn)程C,線(xiàn)程C等待線(xiàn)程D,線(xiàn)程D又在等待線(xiàn)程A。線(xiàn)程A為了檢測(cè)死鎖,它需要遞進(jìn)地檢測(cè)所有被B請(qǐng)求的鎖。從線(xiàn)程B所請(qǐng)求的鎖開(kāi)始,線(xiàn)程A找到了線(xiàn)程C,然后又找到了線(xiàn)程D,發(fā)現(xiàn)線(xiàn)程D請(qǐng)求的鎖被線(xiàn)程A自己持有著。這是它就知道發(fā)生了死鎖。
那么當(dāng)檢測(cè)出死鎖時(shí),這些線(xiàn)程該做些什么呢?
一個(gè)可行的做法是釋放所有鎖,回退,并且等待一段隨機(jī)的時(shí)間后重試。這個(gè)和簡(jiǎn)單的加鎖超時(shí)類(lèi)似,不一樣的是只有死鎖已經(jīng)發(fā)生了才回退,而不會(huì)是因?yàn)榧渔i的請(qǐng)求超時(shí)了。雖然有回退和等待,但是如果有大量的線(xiàn)程競(jìng)爭(zhēng)同一批鎖,它們還是會(huì)重復(fù)地死鎖。
一個(gè)更好的方案是給這些線(xiàn)程設(shè)置優(yōu)先級(jí),讓一個(gè)(或幾個(gè))線(xiàn)程回退,剩下的線(xiàn)程就像沒(méi)發(fā)生死鎖一樣繼續(xù)保持著它們需要的鎖。如果賦予這些線(xiàn)程的優(yōu)先級(jí)是固定不變的,同一批線(xiàn)程總是會(huì)擁有更高的優(yōu)先級(jí)。為避免這個(gè)問(wèn)題,可以在死鎖發(fā)生的時(shí)候設(shè)置隨機(jī)的優(yōu)先級(jí)。
相關(guān)文章
java編譯時(shí)出現(xiàn)使用了未經(jīng)檢查或不安全的操作解決方法
這篇文章主要介紹了java編譯時(shí)出現(xiàn)使用了未經(jīng)檢查或不安全的操作的解決方法,需要的朋友可以參考下2014-03-03SpringBoot使用前綴樹(shù)實(shí)現(xiàn)敏感詞過(guò)濾示例
最近項(xiàng)目用到了敏感詞過(guò)濾,本文主要就來(lái)介紹一下SpringBoot使用前綴樹(shù)實(shí)現(xiàn)敏感詞過(guò)濾示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10springboot實(shí)現(xiàn)訪(fǎng)問(wèn)多個(gè)redis庫(kù)
這篇文章主要介紹了springboot實(shí)現(xiàn)訪(fǎng)問(wèn)多個(gè)redis庫(kù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Java實(shí)現(xiàn)優(yōu)雅的參數(shù)校驗(yàn)方法詳解
這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言如何實(shí)現(xiàn)優(yōu)雅的參數(shù)校驗(yàn),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定是幫助,需要的可以參考一下2022-06-06使用Maven進(jìn)行依賴(lài)漏洞檢查的實(shí)現(xiàn)指南
在現(xiàn)代軟件開(kāi)發(fā)中,開(kāi)源庫(kù)的使用變得愈加普遍和重要,然而,這些開(kāi)源庫(kù)中的漏洞往往會(huì)成為潛在的安全風(fēng)險(xiǎn),在本文中,我們將探討如何使用 Maven 進(jìn)行依賴(lài)漏洞檢查,以確保項(xiàng)目的安全性和穩(wěn)定性,需要的朋友可以參考下2024-05-05Spring的@RequestParam對(duì)象綁定方式
這篇文章主要介紹了Spring的@RequestParam對(duì)象綁定方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10JDK 7U15在 Windows x86平臺(tái)下的安裝方法
本文給大家分享的是如何在windows平臺(tái)下安裝JDK最新版的方法,十分的簡(jiǎn)單全面,有需要的小伙伴可以參考下2016-05-05SpringBoot 將多個(gè)Excel打包下載的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot 將多個(gè)Excel打包下載的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12