當前位置:
首頁 > 知識 > 競態與死鎖的詳解!

競態與死鎖的詳解!

1、競態條件:

  • 定義:競態條件指的是一種特殊的情況,在這種情況下各個執行單元以一種沒有邏輯的順序執行動作,從而導致意想不到的結果。

  • 舉例1:線程T修改資源R後,釋放了它對R的寫訪問權,之後又重新奪回R的讀訪問權再使用它,並以為它的狀態仍然保持在它釋放它之後的狀態。但是在寫訪問權釋放後到重新奪回讀訪問權的這段時間間隔中,可能另一個線程已經修改了R的狀態。(寫——讀之間,該變數已經被其他線程修改

  • 舉例2:另一個經典的競態條件的例子就是生產者/消費者模型。生產者通常使用同一個物理內存空間保存被生產的信息。一般說來,我們不會忘記在生產者與消費者的並發訪問之間保護這個空間。容易被我們忘記的是生產者必須確保在生產新信息前,舊的信息已被消費者所讀取。如果我們沒有採取相應的預防措施,我們將面臨生產的信息從未被消費的危險。(生產者生產出來的信息,消費者未消費

  • 危害漏洞:如果靜態條件沒有被妥善的管理,將導致安全系統的漏洞。同一個應用程序的另一個實例很可能會引發一系列開發者所預計不到的事件。一般來說,必須對那種用於確認身份鑒別結果的布爾量的寫訪問做最完善的保護。如果沒有這麼做,那麼在它的狀態被身份鑒別機制設置後,到它被讀取以保護對資源的訪問的這段時間內,很有可能已經被修改了。已知的安全漏洞很多都歸咎於對靜態條件不恰當的管理。其中之一甚至影響了Unix操作系統的內核。

競態與死鎖的詳解!

2、死鎖:

(1)死鎖介紹

  • 定義:死鎖指的是由於兩個或多個執行單元之間相互等待對方結束而引起阻塞的情況。每個線程都擁有其他線程所需要的資源,同時又等待其他線程已經擁有的資源,並且每個線程在獲取所有需要資源之前都不會釋放自己已經擁有的資源。

  • 舉例1:一個線程T1獲得了對資源R1的訪問權,一個線程T2獲得了對資源R2的訪問權,T1請求對R2的訪問權但是由於此權力被T2所佔而不得不等待,T2請求對R1的訪問權但是由於此權力被T1所佔而不得不等待。T1和T2將永遠維持等待狀態,此時我們陷入了死鎖的處境!這種問題比你所遇到的大多數的bug都要隱秘,

  • 舉例2:打電話雙方,互相撥號,同時佔用通信信道,互相等待。

  • 幾種解決方案

  • 1.在同一時刻不允許一個線程訪問多個資源。避免一個線程在鎖內同時佔用多個資源,盡量保證每個鎖只佔用一個資源。

  • 2.為資源訪問權的獲取定義一個關係順序。換句話說,當一個線程已經獲得了R1的訪問權後,將無法獲得R2的訪問權。當然,訪問權的釋放必須遵循相反的順序。

  • 3.為所有訪問資源的請求系統地定義一個最大等待時間(超時時間),並妥善處理請求失敗的情況。嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。

  • 4.對於資料庫鎖,加鎖和解鎖必須在一個資料庫連接里,否則會出現解鎖失敗的情況。

  • 5.避免同一個線程同時獲取多個鎖。

  • 分析:前兩種技術效率更高但是也更加難於實現。事實上,它們都需要很強的約束,而這點隨著應用程序的演變將越來越難以維護。儘管如此,使用這些技術不會存在失敗的情況。

大的項目通常使用第三種方法。事實上,如果項目很大,一般來說它會使用大量的資源。在這種情況下,資源之間發生衝突的概率很低,也就意味著失敗的情況會比較罕見。我們認為這是一種樂觀的方法。

(2)死鎖的情形

  • 1.資料庫系統的設計:檢測到一組事務發生死鎖時(通過在表示等待關係的有向圖中搜索循環),將選擇一個犧牲者並放棄這個事務,作為犧牲者的事務會釋放自己持有的資源,從而使得其他事務能夠繼續進行。後面應用程序可以重新執行被強制中止的事務。

  • 2.JVM發生死鎖的時候,發生死鎖的線程就永遠不能再使用,只能中止並重啟引用程序。

2、死鎖分類(鎖順序死鎖、資源死鎖)

(1)鎖順序死鎖:

1.鎖順序死鎖出現的原因:使用加鎖機制來確保線程安全,但是過渡的使用加鎖。比如,線程A先鎖住left,後嘗試鎖住right,而線程B先鎖住right,後嘗試鎖住left,則A和B都各自擁有資源left和right,互相等待對方的資源,所以永久等待。

2.鎖順序死鎖分析

  • 兩個線程試圖以不同的順序來獲取相同的鎖,就會發生死鎖。如果按照相同的順序來請求鎖,就不會出現循環的加鎖依賴性,因此就不會出現死鎖。

  • 如果所有的線程都以相同的順序來獲取鎖,程序不會出現順序性死鎖,可以通過定義獲得鎖的順序來避免死鎖。

  • 如果在持有鎖的情況下調用某個外部的方法,那麼就需要警惕活躍性問題,因為在這個外部方法中可能會獲取其他的鎖(可能產生死鎖),或者阻塞時間過長,導致其他的線程無法及時獲得當前被持有的鎖。

3.解決方法:開放調用,在調用外部某個方法時,不需要持有鎖。

(2)資源死鎖:

1.資源死鎖出現的原因:使用線程池和信號量限制對資源的使用。

2.資源死鎖的情形(資源池,資料庫連接池中):

  • 正如當多個線程互相持有彼此正在等待的鎖而又不釋放自己已持有鎖時發生死鎖,當多個線程在相同資源集合上等待時,也可能會發生死鎖。如,線程A持有資料庫C的連接,並等待資料庫D的連接;而線程B持有資料庫D的連接,並等待與資料庫C的連接。(資源池越大,出現該情況概率越小)

  • 另一種形式的資源死鎖是線程飢餓死鎖,一個任務提交另一個任務,並等待被提交恩物在單線程中的執行完成,第一個任務就一直等待,並使得另個任務以及這個Executor中執行的其他任務都停止執行。如果某些任務需要等待其他任務的結果,那麼這些任務往往是產生飢餓死鎖的主要矛盾,有界線程池/資源池與互相依賴的任務不能一起使用。

3.解決:有界線程池/資源池與互相依賴的任務不能一起使用。

3、死鎖的避免與診斷

(1)支持定時的鎖:

顯式的使用Lock類中的定時tryLock功能來代替內置鎖機制(使用內置鎖時,只要沒有獲得鎖,就會永遠等待),而顯式鎖會指定一個超時時限,在等待超過該時間後,tryLock會返回一個失敗信息。如果不能獲得所需要的鎖,定時的鎖或者輪詢鎖會釋放已經得到的鎖,然後重新嘗試獲得所有的鎖。(失敗的記錄會被記錄到日誌中,並採取措施),這樣技術只有在同時獲得兩個鎖時才有效,如果在嵌套的方法中請求多個鎖,那麼即使你知道已經持有了外層鎖,也無法釋放它。

(2)通過線程轉儲信息來分析死鎖:

JVM可以通過線程轉儲來幫助識別死鎖的發生。線程的轉儲包括各個運行中的線程的棧追蹤信息,同時包含加鎖信息,例如每個線程持有哪些鎖,在哪些棧幀中獲得這些鎖,以及被阻塞的線程正在等待獲取那個鎖,在生成線程轉儲信息之前,JVM將在等待關係圖中通過搜索循環來找死鎖。如果發現一個死鎖,則獲取相應的死鎖信息,如在死鎖中涉及哪些鎖和線程,以及這個鎖的獲取操作位於程序的哪些位置。

4、其他的活躍性危險:

(1)飢餓

1.飢餓出現原因:當前線程由於無法訪問所需要的資源而不能繼續執行的時候,就發生飢餓,引發飢餓最常見的資源就是CPU時鐘周期。還有就是Java程序中對線程的優先順序使用不當,或者某個線程持有鎖時,無限制的執行一些無法結束的結構(無限循環或者無限制地等待某個資源),導致其他需要獲得該鎖的線程無法獲取它而導致飢餓。

2.解決方案:要避免使用線程優先順序,因為改變線程的優先順序會增加平台依賴性,並導致活躍性問題。使用默認的優先順序就行了。

(2)糟糕的響應性

不良的鎖管理會導致糟糕的響應性;在GUI應用程序中使用後台線程會導致糟糕的響應性,後台線程會與事件線程共同競爭CPU的時鐘周期。。

(3)活鎖

1.活鎖出現的原因1:該問題儘管不會阻塞線程,但是不能繼續執行,因為線程總是不斷重複執行相同的操作,而且總失敗,通常發生在處理事務消息的應用程序中。

舉例1:處理事務消息的引用程序不能成功處理某個消息,消息處理機制就回滾整個事務,並將它重新放到隊列的開頭,消息處理器在處理這個消息時存在錯誤並導致失敗,然後每次這個消息都從隊列中取出並傳遞到存在錯誤的處理器時,就會發生事務的回滾,處理器反覆被調用並返回相同的結果(毒藥消息)。處理器並沒有被阻塞,但是無法繼續執行下去。(過度的錯誤恢復代碼,不可修復的錯誤作為可修復的錯誤)。

2.活鎖出現的原因2:當多個相互協作的線程都對彼此進行響應從而修改各自的狀態,並使得任何一個線程都無法繼續執行下去,就發生活鎖。

舉例2:兩個過於禮貌的人在半路上面對面相遇,彼此都讓出對方的路,然後在另一條路上又相遇了,反覆避讓下去。

3.解決方案:要解決活鎖問題,需要在重試機制中引入隨機性。如網路中兩台機器需要用相同載波發送數據包,數據包發送衝突,然後過段時間重發又發生衝突,並不斷衝突下去,如果使用隨機指數退避演算法,就不會發生衝突。

在並發應用中,通過等待隨機長度的時間和回退可以有效地避免活鎖的發生。

5、死鎖的條件

1.死鎖多個進程或線程競爭某一共享資源,而出現的一種互相等待的現象

2.產生死鎖的主要原因:1 系統資源不夠 2 進程或者線程運行推進的順序不合適3 資源分配不當

3.死鎖的四個必要條件

  • 1 互斥條件:一個資源每次只能被一個進程使用。(一個資源只被一個進程使用)任務使用的資源至少有一個是不能共享的。

  • 2 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不變。(自己的資源不釋放,等待所需要的其他資源)至少有一個任務它必須持有一個資源且正在等待獲取一個當前被別的任務持有的資源。

  • 3 不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪。(自己的資源,使用完前不被剝奪)資源不能被任務搶佔,任務必須把資源釋放當做普通事件。

  • 4 循環等待條件:若干進程之間形成的一種頭尾相接的循環等待資源關係。(進程之間循環等待資源)必須有循環等待,這時,一個任務等待其他任務所持有的資源,後者又在等待另一個任務所持有的資源,這樣一直下去,直到有一個任務在等待第一個任務所持有的資源,大家都被鎖住。

只要上述條件之一不滿足,就不會產生死鎖。防止死鎖最容易的是破壞第4個條件,只要改變等待資源的順序即可。

6、預防死鎖

防止死鎖的發生只需破壞死鎖產生的四個必要條件之一即可。

1) 破壞互斥條件

  • 如果允許系統資源都能共享使用,則系統不會進入死鎖狀態。但有些資源根本不能同時訪問,如印表機等臨界資源只能互斥使用。所以,破壞互斥條件而預防死鎖的方法不太可行,而且在有的場合應該保護這種互斥性。

2) 破壞不剝奪條件

  • 當一個已保持了某些不可剝奪資源的進程,請求新的資源而得不到滿足時,它必須釋放已經保持的所有資源,待以後需要時再重新申請。這意味著,一個進程已佔有的資源會被暫時釋放,或者說是被剝奪了,或從而破壞了不可剝奪條件。

  • 該策略實現起來比較複雜,釋放已獲得的資源可能造成前一階段工作的失效,反覆地申請和釋放資源會增加系統開銷,降低系統吞吐量。這種方法常用於狀態易於保存和恢復的資源,如CPU的寄存器及內存資源,一般不能用於印表機之類的資源。

3) 破壞請求和保持條件

  • 釆用預先靜態分配方法,即進程在運行前一次申請完它所需要的全部資源,在它的資源未滿足前,不把它投入運行。一旦投入運行後,這些資源就一直歸它所有,也不再提出其他資源請求,這樣就可以保證系統不會發生死鎖。

  • 這種方式實現簡單,但缺點也顯而易見,系統資源被嚴重浪費,其中有些資源可能僅在運行初期或運行快結束時才使用,甚至根本不使用。而且還會導致「飢餓」現象,當由於個別資源長期被其他進程佔用時,將致使等待該資源的進程遲遲不能開始運行。

4) 破壞循環等待條件

  • 為了破壞循環等待條件,可釆用順序資源分配法。首先給系統中的資源編號,規定每個進程,必須按編號遞增的順序請求資源,同類資源一次申請完。也就是說,只要進程提出申請分配資源Ri,則該進程在以後的資源申請中,只能申請編號大於Ri的資源。

  • 這種方法存在的問題是,編號必須相對穩定,這就限制了新類型設備的增加;儘管在為資源編號時已考慮到大多數作業實際使用這些資源的順序,但也經常會發生作業使用資源的順序與系統規定順序不同的情況,造成資源的浪費;此外,這種按規定次序申請資源的方法,也必然會給用戶的編程帶來麻煩。

上面我們講到的死鎖預防是排除死鎖的靜態策略,它使產生死鎖的四個必要條件不能同時具備,從而對進程申請資源的活動加以限制,以保證死鎖不會發生。下面我們介紹排除死鎖的動態策略--死鎖的避免,它不限制進程有關申請資源的命令,而是對進程所發出的每一個申請資源命令加以動態地檢查,並根據檢查結果決定是否進行資源分配。就是說,在資源分配過程中若預測有發生死鎖的可能性,則加以避免。這種方法的關鍵是確定資源分配的安全性。

7、安全序列

我們首先引入安全序列的定義:所謂系統是安全的,是指系統中的所有進程能夠按照某一種次序分配資源,並且依次地運行完畢,這種進程序列{P1,P2,...,Pn}就是安全序列。如果存在這樣一個安全序列,則系統是安全的;如果系統不存在這樣一個安全序列,則系統是不安全的。

安全序列{P1,P2,...,Pn}是這樣組成的:若對於每一個進程Pi,它需要的附加資源可以被系統中當前可用資源加上所有進程Pj當前佔有資源之和所滿足,則{P1,P2,...,Pn}為一個安全序列,這時系統處於安全狀態,不會進入死鎖狀態。

雖然存在安全序列時一定不會有死鎖發生,但是系統進入不安全狀態(四個死鎖的必要條件同時發生)也未必會產生死鎖。當然,產生死鎖後,系統一定處於不安全狀態。

8、銀行家演算法

這是一個著名的避免死鎖的演算法,是由Dijstra首先提出來並加以解決的。

(1)背景知識

一個銀行家如何將一定數目的資金安全地借給若干個客戶,使這些客戶既能借到錢完成要乾的事,同時銀行家又能收回全部資金而不至於破產,這就是銀行家問題。這個問題同操作系統中資源分配問題十分相似:銀行家就像一個操作系統,客戶就像運行的進程,銀行家的資金就是系統的資源。

(2)問題的描述

一個銀行家擁有一定數量的資金,有若干個客戶要貸款。每個客戶須在一開始就聲明他所需貸款的總額。若該客戶貸款總額不超過銀行家的資金總數,銀行家可以接收客戶的要求。客戶貸款是以每次一個資金單位(如1萬RMB等)的方式進行的,客戶在借滿所需的全部單位款額之前可能會等待,但銀行家須保證這種等待是有限的,可完成的。

例如:有三個客戶C1,C2,C3,向銀行家借款,該銀行家的資金總額為10個資金單位,其中C1客戶要借9各資金單位,C2客戶要借3個資金單位,C3客戶要借8個資金單位,總計20個資金單位。某一時刻的狀態如圖所示。


C1 2(7)C2 2(1)C3 4(4)餘額2 C1 2(7)C3 4(4)餘額4 C1 2(7)餘額8 餘額10
(a) (b) (c) (d)

銀行家演算法示意圖

對於a圖的狀態,按照安全序列的要求,我們選的第一個客戶應滿足該客戶所需的貸款小於等於銀行家當前所剩餘的錢款,可以看出只有C2客戶能被滿足:C2客戶需1個資金單位,小銀行家手中的2個資金單位,於是銀行家把1個資金單位借給C2客戶,使之完成工作並歸還所借的3個資金單位的錢,進入b圖。同理,銀行家把4個資金單位借給C3客戶,使其完成工作,在c圖中,只剩一個客戶C1,它需7個資金單位,這時銀行家有8個資金單位,所以C1也能順利借到錢並完成工作。最後(見圖d)銀行家收回全部10個資金單位,保證不賠本。那麽客戶序列{C1,C2,C3}就是個安全序列(C2、C3、C1),按照這個序列貸款,銀行家才是安全的。否則的話,若在圖b狀態時,銀行家把手中的4個資金單位借給了C1,則出現不安全狀態:這時C1,C3均不能完成工作,而銀行家手中又沒有錢了,系統陷入僵持局面,銀行家也不能收回投資。

綜上所述,銀行家演算法是從當前狀態出發,逐個按安全序列檢查各客戶誰能完成其工作,然後假定其完成工作且歸還全部貸款,再進而檢查下一個能完成工作的客戶,......。如果所有客戶都能完成工作,則找到一個安全序列,銀行家才是安全的。

從上面分析看出,銀行家演算法允許死鎖必要條件中的互斥條件,佔有且申請條件,不可搶佔條件的存在,這樣,它與預防死鎖的幾種方法相比較,限制條件少了,資源利用程度提高了。

這是該演算法的優點。其缺點是:

〈1〉這個演算法要求客戶數保持固定不變,這在多道程序系統中是難以做到的。

〈2〉這個演算法保證所有客戶在有限的時間內得到滿足,但實時客戶要求快速響應,所以要考慮這個因素。

〈3〉由於要尋找一個安全序列,實際上增加了系統的開銷

9、死鎖的檢測和解除

先前的死鎖預防以及避免演算法都是在進程分配資源時施加限制條件或進行檢測,若系統為進程分配資源時不採取任何措施,就應該提供死鎖檢測和解除的手段。

(1)資源分配圖

系統死鎖,可利用資源分配圖來描述。如圖2-17所示,用圓圈代表一個進程,用框代表一類資源。由於一種類型的資源可能有多個,用框中的一個點代表一類資源中的一個資源。從進程到資源的有向邊叫請求邊,表示該進程申請一個單位的該類資源;從資源到進程的邊叫分配邊,表示該類資源已經有一個資源被分配給了該進程。

競態與死鎖的詳解!

在圖2-17所示的資源分配圖中,進程P1已經分得了兩個R1資源,並又請求一個R2 資源;進程P2分得了一個R1和一個R2資源,並又請求一個R1資源。

(2)死鎖定理

通過簡化資源分配圖的方法檢測系統狀態S是否為死鎖狀態。簡化步驟如下:

競態與死鎖的詳解!

  • 1) 在資源分配圖中,找出既不阻塞又不是孤點的進程Pi(即找出一條有向邊與它相連,且該有向邊對應資源的申請數量小於等於系統中已有空閑資源數量。若所有的連接該進程的邊均滿足上述條件,則這個進程能繼續運行直至完成,然後釋放它所佔有的所有資源)。消去它所有的請求邊和分配邊,使之成為孤立的結點。在圖2-18(a)中,P1是滿足這一條件的進程結點,將P1的所有邊消去,便得到圖(b)所示的情況。

  • 2) 進程Pi所釋放的資源,可以喚醒某些因等待這些資源而阻塞的進程,原來的阻塞進程可能變為非阻塞進程。如圖2-18(c)所示。

  • S為死鎖的條件是當且僅當S狀態的資源分配圖是不可完全簡化的,該條件為死鎖定理。

(3)死鎖的解除

一旦檢測出死鎖,就應立即釆取相應的措施,以解除死鎖。死鎖解除的主要方法有:

  • 1) 資源剝奪法。掛起某些死鎖進程,並搶佔它的資源,將這些資源分配給其他的死鎖進程。但應防止被掛起的進程長時間得不到資源,而處於資源匱乏的狀態。

  • 2) 撤銷進程法。強制撤銷部分、甚至全部死鎖進程並剝奪這些進程的資源。撤銷的原則可以按進程優先順序和撤銷進程代價的高低進行。

  • 3) 進程回退法。讓一(多)個進程回退到足以迴避死鎖的地步,進程回退時自願釋放資源而不是被剝奪。要求系統保持進程的歷史信息,設置還原點。

(4)避免死鎖的方法

1)避免一個線程同時獲取多個鎖;

2)避免一個線程在鎖內同時佔用多個資源,盡量保證每個鎖只佔用一個資源;

3)嘗試使用定時鎖,使用lock.tryLock(timeout)來代替使用內部鎖機制;

4)對於資料庫鎖,加鎖和解鎖必須在一個資料庫連接里,否則會出現解鎖失敗的情況。

10、死鎖代碼實現步驟

1)兩個線程裡面分別持有兩個Object對象:lock1和lock2,這兩個lock作為同步代碼塊的鎖;

2)線程1的run()方法中同步代碼快先獲取lock1的對象所,Thread.sleep(),時間不需要太多,50毫秒差不多;然後接著獲取lock2的對象鎖。(sleep可以防止線程1啟動一下子連續獲得lock1和lock2兩個對象的對象鎖;

3)線程2的run()方法中同步代碼塊先獲取lock2的對象鎖,接著獲取lock1的對象鎖,這時lock1的對象鎖已經被線程1鎖持有,線程2肯定是要等待線程1釋放lock1的對象鎖的;

4)線程1睡完後,線程2已經獲取lock2的對象鎖,線程1此時嘗試獲取lock2的對象鎖便被阻塞,而線程2準備獲取線程1的對象鎖時,也被阻塞。


更多IT精品課程,訪問中公優就業官網:http://xue.ujiuye.com

勤工儉學計劃」,給你一個真正0元學習IT技術的機會!

http://www.ujiuye.com/zt/qgjx/?wt.bd=lsh

找工作太難?不是你不行,我們來幫你!

http://www.ujiuye.com/zt/jyfc/?wt.bd=lsh

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 IT優就業 的精彩文章:

JVM學習記錄:垃圾收集演算法
回首2010,是什麼決定了BAT三巨頭的格局?
有人告訴我一篇文章就可以學會Gulp(Getting started with Gulp)!你敢信?

TAG:IT優就業 |

您可能感興趣

體式詳解:解鎖輪式練習的奧秘,讓你的腰腹靈活有力
如何精準的告訴隊友敵人的位置?刺激戰場精準報點詳解,你真的了解嗎?
從曹操之死詳解偏頭痛的藥物治療
挽回攻略:詳解挽回男朋友的正確心態
胎記的位置與命運詳解
你真的了解患者的骨盆嗎?看案例詳解!
詳解蜀國滅亡的真正原因,魏延之死究竟是得罪了誰?
正確護膚詳解:你的爽膚水真的選對了嗎?
詳解糞便潛血檢測的結果解讀和意義
三個令和假面騎士變身過程詳解,女騎太優雅,零一多形態曝光!
堅持練習武術的人,後來都怎麼樣了?附:易筋經詳解!
正確護膚詳解:精油真的有那麼神奇嗎?
最強肌肉訓練詳解,從此再也不用擔心訓練失誤啦!
相術精髓:詳解面相「八殺」與運勢吉凶!
裁判手勢詳解圖!看不懂的速來了解~
正確護膚詳解:防晒霜你真的用對了嗎?
詳解愛情和婚姻究竟是什麼?
嘴歪的面相詳解
詳解多發性腦梗塞與老年痴呆的誘因和危害
打坐:靜功的修習要點與手印,詳解關節