摘要:本文將展示三種用于避免死鎖的技術(shù)加鎖順序加鎖時(shí)限死鎖檢測加鎖順序當(dāng)多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發(fā)生。
死鎖是兩個(gè)或更多線程阻塞著等待其它處于死鎖狀態(tài)的線程所持有的鎖。死鎖通常發(fā)生在多個(gè)線程同時(shí)但以不同的順序請求同一組鎖的時(shí)候。死鎖原理請參考此文。
在有些情況下死鎖是可以避免的。本文將展示三種用于避免死鎖的技術(shù):
加鎖順序
加鎖時(shí)限
死鎖檢測
加鎖順序當(dāng)多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發(fā)生。
如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會(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è)線程(比如線程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。
例如,線程2和線程3只有在獲取了鎖A之后才能嘗試獲取鎖C(譯者注:獲取鎖A是獲取鎖C的必要條件)。因?yàn)榫€程1已經(jīng)擁有了鎖A,所以線程2和3需要一直等到鎖A被釋放。然后在它們嘗試對B或C加鎖之前,必須成功地對A加了鎖。
按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。但是,這種方式需要你事先知道所有可能會(huì)用到的鎖(譯者注:并對這些鎖做適當(dāng)?shù)呐判?/em>),但總有些時(shí)候是無法預(yù)知的。
加鎖時(shí)限另外一個(gè)可以避免死鎖的方法是在嘗試獲取鎖的時(shí)候加一個(gè)超時(shí)時(shí)間,這也就意味著在嘗試獲取鎖的過程中若超過了這個(gè)時(shí)限該線程則放棄對該鎖請求。若一個(gè)線程沒有在給定的時(shí)限內(nèi)成功獲得所有需要的鎖,則會(huì)進(jìn)行回退并釋放所有已經(jīng)獲得的鎖,然后等待一段隨機(jī)的時(shí)間再重試。這段隨機(jī)的等待時(shí)間讓其它線程有機(jī)會(huì)嘗試獲取相同的這些鎖,并且讓該應(yīng)用在沒有獲得鎖的時(shí)候可以繼續(xù)運(yùn)行(譯者注:加鎖超時(shí)后可以先繼續(xù)運(yùn)行干點(diǎn)其它事情,再回頭來重復(fù)之前加鎖的邏輯)。
以下是一個(gè)例子,展示了兩個(gè)線程以不同的順序嘗試獲取相同的兩個(gè)鎖,在發(fā)生超時(shí)后回退并重試的場景:
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.
在上面的例子中,線程2比線程1早200毫秒進(jìn)行重試加鎖,因此它可以先成功地獲取到兩個(gè)鎖。這時(shí),線程1嘗試獲取鎖A并且處于等待狀態(tài)。當(dāng)線程2結(jié)束時(shí),線程1也可以順利的獲得這兩個(gè)鎖(除非線程2或者其它線程在線程1成功獲得兩個(gè)鎖之前又獲得其中的一些鎖)。
需要注意的是,由于存在鎖的超時(shí),所以我們不能認(rèn)為這種場景就一定是出現(xiàn)了死鎖。也可能是因?yàn)楂@得了鎖的線程(導(dǎo)致其它線程超時(shí))需要很長的時(shí)間去完成它的任務(wù)。
此外,如果有非常多的線程同一時(shí)間去競爭同一批資源,就算有超時(shí)和回退機(jī)制,還是可能會(huì)導(dǎo)致這些線程重復(fù)地嘗試但卻始終得不到鎖。如果只有兩個(gè)線程,并且重試的超時(shí)時(shí)間設(shè)定為0到500毫秒之間,這種現(xiàn)象可能不會(huì)發(fā)生,但是如果是10個(gè)或20個(gè)線程情況就不同了。因?yàn)檫@些線程等待相等的重試時(shí)間的概率就高的多(或者非常接近以至于會(huì)出現(xiàn)問題)。
(譯者注:超時(shí)和重試機(jī)制是為了避免在同一時(shí)間出現(xiàn)的競爭,但是當(dāng)線程很多時(shí),其中兩個(gè)或多個(gè)線程的超時(shí)時(shí)間一樣或者接近的可能性就會(huì)很大,因此就算出現(xiàn)競爭而導(dǎo)致超時(shí)后,由于超時(shí)時(shí)間一樣,它們又會(huì)同時(shí)開始重試,導(dǎo)致新一輪的競爭,帶來了新的問題。)
這種機(jī)制存在一個(gè)問題,在Java中不能對synchronized同步塊設(shè)置超時(shí)時(shí)間。你需要?jiǎng)?chuàng)建一個(gè)自定義鎖,或使用Java5中java.util.concurrent包下的工具。寫一個(gè)自定義鎖類不復(fù)雜,但超出了本文的內(nèi)容。后續(xù)的Java并發(fā)系列會(huì)涵蓋自定義鎖的內(nèi)容。
死鎖檢測死鎖檢測是一個(gè)更好的死鎖預(yù)防機(jī)制,它主要是針對那些不可能實(shí)現(xiàn)按序加鎖并且鎖超時(shí)也不可行的場景。
每當(dāng)一個(gè)線程獲得了鎖,會(huì)在線程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map、graph等等)將其記下。除此之外,每當(dāng)有線程請求鎖,也需要記錄在這個(gè)數(shù)據(jù)結(jié)構(gòu)中。
當(dāng)一個(gè)線程請求鎖失敗時(shí),這個(gè)線程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生。例如,線程A請求鎖7,但是鎖7這個(gè)時(shí)候被線程B持有,這時(shí)線程A就可以檢查一下線程B是否已經(jīng)請求了線程A當(dāng)前所持有的鎖。如果線程B確實(shí)有這樣的請求,那么就是發(fā)生了死鎖(線程A擁有鎖1,請求鎖7;線程B擁有鎖7,請求鎖1)。
當(dāng)然,死鎖一般要比兩個(gè)線程互相持有對方的鎖這種情況要復(fù)雜的多。線程A等待線程B,線程B等待線程C,線程C等待線程D,線程D又在等待線程A。線程A為了檢測死鎖,它需要遞進(jìn)地檢測所有被B請求的鎖。從線程B所請求的鎖開始,線程A找到了線程C,然后又找到了線程D,發(fā)現(xiàn)線程D請求的鎖被線程A自己持有著。這是它就知道發(fā)生了死鎖。
下面是一幅關(guān)于四個(gè)線程(A,B,C和D)之間鎖占有和請求的關(guān)系圖。像這樣的數(shù)據(jù)結(jié)構(gòu)就可以被用來檢測死鎖。
那么當(dāng)檢測出死鎖時(shí),這些線程該做些什么呢?
一個(gè)可行的做法是釋放所有鎖,回退,并且等待一段隨機(jī)的時(shí)間后重試。這個(gè)和簡單的加鎖超時(shí)類似,不一樣的是只有死鎖已經(jīng)發(fā)生了才回退,而不會(huì)是因?yàn)榧渔i的請求超時(shí)了。雖然有回退和等待,但是如果有大量的線程競爭同一批鎖,它們還是會(huì)重復(fù)地死鎖(編者注:原因同超時(shí)類似,不能從根本上減輕競爭)。
一個(gè)更好的方案是給這些線程設(shè)置優(yōu)先級,讓一個(gè)(或幾個(gè))線程回退,剩下的線程就像沒發(fā)生死鎖一樣繼續(xù)保持著它們需要的鎖。如果賦予這些線程的優(yōu)先級是固定不變的,同一批線程總是會(huì)擁有更高的優(yōu)先級。為避免這個(gè)問題,可以在死鎖發(fā)生的時(shí)候設(shè)置隨機(jī)的優(yōu)先級。
原文 Deadlock Prevention
譯者:申章 ??校對:丁一
via ifeve
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/64038.html
摘要:線程間通信其實(shí)就是多個(gè)線程操作同一個(gè)資源,但動(dòng)作不同。同步前提是多線程。將該線程載入線程池,等待喚醒。該方法拋出異常,故需要配合使用隨機(jī)喚醒線程池中一線程。線程為了檢測死鎖,它需要遞進(jìn)地檢測所有被請求的鎖。 線程間通信 其實(shí)就是多個(gè)線程操作同一個(gè)資源,但動(dòng)作不同。示例:在某個(gè)數(shù)據(jù)庫中,Input輸入人的姓名,性別,Output輸出,兩個(gè)線程同時(shí)作用。思考:1.明確哪些代碼是多線程操作的...
摘要:此時(shí)線程需要鎖才能繼續(xù)往下執(zhí)行。但是線程的鎖并沒有釋放,線程的鎖也沒有釋放。 前言 只有光頭才能變強(qiáng) 回顧前面: ThreadLocal就是這么簡單 多線程三分鐘就可以入個(gè)門了! 多線程基礎(chǔ)必要知識(shí)點(diǎn)!看了學(xué)習(xí)多線程事半功倍 Java鎖機(jī)制了解一下 AQS簡簡單單過一遍 Lock鎖子類了解一下 線程池你真不來了解一下嗎? 本篇主要是講解死鎖,這是我在多線程的最后一篇了。主要將多線程...
摘要:關(guān)于串行化與一致性的關(guān)系數(shù)據(jù)庫并發(fā)控制的基本目標(biāo)是確保事務(wù)的并發(fā)執(zhí)行不會(huì)導(dǎo)致數(shù)據(jù)庫一致性的丟失。該請求發(fā)送給并發(fā)控制管理器,只有并發(fā)控制管理器授予所需鎖后,事務(wù)才能繼續(xù)其操作。 全文主要參考數(shù)據(jù)庫系統(tǒng)概念一書以及mooc上戰(zhàn)德臣老師的數(shù)據(jù)庫課程 事務(wù)最基本的特性之一是隔離性,當(dāng)數(shù)據(jù)庫中有多個(gè)事務(wù)并發(fā)執(zhí)行的時(shí)候,隔離性不一定能保持。為了保持事務(wù)的隔離性,系統(tǒng)必須對并發(fā)事務(wù)之間的相互作用...
摘要:例如,張三同時(shí)申請賬本和,賬本管理員如果發(fā)現(xiàn)文件架上只有賬本,這個(gè)時(shí)候賬本管理員是不會(huì)把賬本拿下來給張三的,只有賬本和都在的時(shí)候才會(huì)給張三。但仍需注意的是,有時(shí)候預(yù)防死鎖成本也是很高的。 在上一篇中,我們嘗試使用了 Account.class作為互斥鎖,來解決轉(zhuǎn)賬問題。但是很容易發(fā)現(xiàn)這樣,所有的轉(zhuǎn)賬操作都是串行的,性能太差了。 讓我們嘗試提升下性能。 向現(xiàn)實(shí)世界要答案 現(xiàn)實(shí)世界中,轉(zhuǎn)賬...
閱讀 1402·2021-11-22 09:34
閱讀 1378·2021-09-22 14:57
閱讀 3400·2021-09-10 10:50
閱讀 1372·2019-08-30 15:54
閱讀 3690·2019-08-29 17:02
閱讀 3472·2019-08-29 12:54
閱讀 2611·2019-08-27 10:57
閱讀 3316·2019-08-26 12:24