多線程學習(1)產生死鎖的條件和解決辦法

死鎖概念及產生原理

    概念:多個併發線程因爭奪系統資源而產生相互等待的現象。java

    原理: 在多線程環境下,某些資源具備互斥性,當被一個線程佔用後,其餘線程沒法獲取該資源;而一個線程爲了完成一個完整的事務須要持有至少兩個資源,且當線程已持有一個資源後發現其餘所需資源被其餘線程持有了,它只能選擇等待其餘線程釋放資源,它再獲取。而其餘線程剛巧也在等待它釋放佔用的資源,那麼它們就互相持有了對方須要的資源,且互相等待對方釋放資源,致使死鎖現象發生。算法

發生死鎖的代碼

/**
 * 測試多線程併發 死鎖
 * 
 */
public class Test {
	public void method1() {
		synchronized (String.class) {
			System.out.println("print String.class");
			synchronized (Integer.class) {
				System.out.println("print Integer.class");
			}
		}
	}

	public void method2() {
		synchronized (Integer.class) {
			System.out.println("print Integer.class");
			synchronized (String.class) {
				System.out.println("print String.class");
			}
		}
	}

	public static void main(String[] args) {
		Thread1 thread1 = new Thread1();
		Thread2 thr2 = new Thread2();
		Thread thread2 = new Thread(thr2);
		thread1.start();
		thread2.start();
	}
}

public class Thread1 extends Thread {

	@Override
	public void run() {
		Test test1 = new Test();
		for (int i = 0; i < 100; i++) {
			test1.method1();
		}
	}
}

public class Thread2 implements Runnable {

	@Override
	public void run() {
		Test test2 = new Test();
		for (int i = 0; i < 100; i++) {
			test2.method2();
		}
	}

}

上面的例子中,常常容易發生死鎖的狀況,是同步塊中嵌套同步快。 能夠看到線程1想要鎖定對象2,它由線程2持有,而線程2想要鎖定對象1,它由線程1保持。因爲沒有線程願意放棄,因此存在死鎖而且Java程序被卡住了。數據庫

如何解決上面的死鎖,能夠經過調整資源的訪問順序,使method1和method2訪問資源順序一致,就不會形成彼此死鎖。數組

死鎖產生的4個必要條件

    一、互斥:某種資源一次只容許一個進程訪問,即該資源一旦分配給某個進程,其餘進程就不能再訪問,直到該進程訪問結束。
    二、佔有且等待:一個進程自己佔有資源(一種或多種),同時還有資源未獲得知足,正在等待其餘進程釋放該資源。
    三、不可剝奪:別人已經佔有了某項資源,你不能由於本身也須要該資源,就去把別人的資源搶過來。
    四、循環等待:在發生死鎖時必然存在一個進程等待隊列{P1,P2,…,Pn},其中P1等待P2佔有的資源,P2等待P3佔有的資源,…,Pn等待P1佔有的資源,造成一個進程等待環路,環路中每個進程所佔有的資源同時被另外一個申請,也就是前一個進程佔有後一個進程所深情地資源。
       當以上四個條件均知足,必然會形成死鎖,發生死鎖的進程沒法進行下去,它們所持有的資源也沒法釋放。這樣會致使CPU的吞吐量降低。因此死鎖狀況是會浪費系統資源和影響計算機的使用性能的。那麼,解決死鎖問題就是至關有必要的了。安全

        數據庫的設計中會檢測死鎖的發生,並嘗試解決死鎖。而JAVA虛擬機,沒辦法自動解決死鎖,死鎖的線程會佔用性能,知道項目從新啓動。咱們能夠經過人工使用虛擬機工具會查到發生死鎖的線程,並經過虛擬機命令強制結束髮生死鎖的線程。數據結構

避免死鎖的方法

一、死鎖預防 ----- 確保系統永遠不會進入死鎖狀態
     產生死鎖須要四個條件,那麼,只要這四個條件中至少有一個條件得不到知足,就不可能發生死鎖了。因爲互斥條件是非共享資源所必須的,不只不能改變,還應加以保證,因此,主要是破壞產生死鎖的其餘三個條件。
a、破壞「佔有且等待」條件
     方法1:全部的進程在開始運行以前,必須一次性地申請其在整個運行過程當中所須要的所有資源。
         優勢:簡單易實施且安全。
         缺點:由於某項資源不知足,進程沒法啓動,而其餘已經知足了的資源也不會獲得利用,嚴重下降了資源的利用率,形成資源浪費。使進程常常發生飢餓現象。
     方法2:該方法是對第一種方法的改進,容許進程只得到運行初期須要的資源,便開始運行,在運行過程當中逐步釋放掉分配到的已經使用完畢的資源,而後再去請求新的資源。這樣的話,資源的利用率會獲得提升,也會減小進程的飢餓問題。多線程

第一種方法靜態分配即每一個進程在開始執行時就申請他所須要的所有資源。第二種是動態分配即每一個進程在申請所須要的資源時他自己不佔用系統資源。併發

b、破壞「不可搶佔」條件
        一個進程不能得到所須要的所有資源時便處於等待狀態,等待期間他佔有的資源將被隱式的釋放從新加入到 系統的資源列表中,能夠被其餘的進程使用,而等待的進程只有從新得到本身原有的資源以及新申請的資源才能夠從新啓動,執行。
      該種方法實現起來比較複雜,且代價也比較大。釋放已經保持的資源頗有可能會致使進程以前的工做失效等,反覆的申請和釋放資源會致使進程的執行被無限的推遲,這不只會延長進程的週轉週期,還會影響系統的吞吐量。
c、破壞「循環等待」條件
     能夠經過定義資源類型的線性順序來預防,可將每一個資源編號,當一個進程佔有編號爲i的資源時,那麼它下一次申請資源只能申請編號大於i的資源。如圖所示:
ide

這樣雖然避免了循環等待,可是這種方法是比較低效的,資源的執行速度回變慢,而且可能在沒有必要的狀況下拒絕資源的訪問,好比說,進程c想要申請資源1,若是資源1並無被其餘進程佔有,此時將它分配個進程c是沒有問題的,可是爲了不產生循環等待,該申請會被拒絕,這樣就下降了資源的利用率。工具

二、避免死鎖 ----- 在使用前進行判斷,只容許不會產生死鎖的進程申請資源
死鎖避免的基本思想:系統對進程發出的每個系統可以知足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源,若是分配後系統可能發生死鎖,則不予分配,不然予以分配,這是一種保證系統不進入死鎖狀態的動態策略。

若是操做系統能保證全部進程在有限時間內獲得須要的所有資源,則系統處於安全狀態不然系統是不安全的。
安全狀態是指:若是系統存在 由全部的安全序列{P1,P2,…Pn},則系統處於安全狀態。一個進程序列是安全的,若是對其中每個進程Pi(i >=1 && i <= n)他之後尚須要的資源不超過系統當前剩餘資源量與全部進程Pj(j < i)當前佔有資源量之和,系統處於安全狀態則不會發生死鎖。
不安全狀態:若是不存在任何一個安全序列,則系統處於不安全狀態。他們之間的對對應關係以下圖所示: 
 è¿™é‡Œå†™å›¾ç‰‡æè¿°
下面咱們來經過一個例子對安全狀態和不安全狀態進行更深的瞭解 

安全状态

如上圖所示系統處於安全狀態,系統剩餘3個資源,能夠把其中的2個分配給P3,此時P3已經得到了全部的資源,執行完畢後還能還給系統4個資源,此時系統剩餘5個資源因此知足(P2所需的資源不超過系統當前剩餘量與P3當前佔有資源量之和),同理P1也能夠在P2執行完畢後得到本身須要的資源。 
若是P1提出再申請一個資源的要求,系統從剩餘的資源中分配一個給進程P1,此時系統剩餘2個資源,新的狀態圖以下:那麼是否還是安全序列呢那咱們來分析一下 

这里写图片描述

系統當前剩餘2個資源,分配給P3後P3執行完畢還給系統4個資源,可是P2須要5個資源,P1須要6個資源,他們都沒法得到資源執行完成,所以找不到一個安全序列。此時系統轉到了不安全狀態。

 

兩種避免辦法:
    一、若是一個進程的請求會致使死鎖,則不啓動該進程
    二、若是一個進程的增長資源請求會致使死鎖 ,則拒絕該申請。

避免死鎖的具體實現一般利用銀行家算法
    銀行家算法
a、銀行家算法的相關數據結構
    可利用資源向量Available:用於表示系統裏邊各類資源剩餘的數目。因爲系統裏邊擁有的資源一般都是有不少種(假設有m種),因此,咱們用一個有m個元素的數組來表示各類資源。數組元素的初始值爲系統裏邊所配置的該類所有可用資源的數目,其數值隨着該類資源的分配與回收動態地改變。
    最大需求矩陣Max:用於表示各個進程對各類資源的額最大需求量。進程可能會有不少個(假設爲n個),那麼,咱們就能夠用一個nxm的矩陣來表示各個進程多各類資源的最大需求量
    分配矩陣Allocation:顧名思義,就是用於表示已經分配給各個進程的各類資源的數目。也是一個nxm的矩陣。
    需求矩陣Need:用於表示進程仍然須要的資源數目,用一個nxm的矩陣表示。系統可能無法一下就知足了某個進程的最大需求(一般進程對資源的最大需求也是隻它在整個運行週期中須要的資源數目,並非每個時刻都須要這麼多),因而,爲了進程的執行可以向前推動,一般,系統會先分配個進程一部分資源保證進程可以執行起來。那麼,進程的最大需求減去已經分配給進程的數目,就獲得了進程仍然須要的資源數目了。

銀行家算法經過對進程需求、佔有和系統擁有資源的實時統計,確保系統在分配給進程資源不會形成死鎖纔會給與分配。
死鎖避免的優勢:不須要死鎖預防中的搶佔和從新運行進程,而且比死鎖預防的限制要少。
死鎖避免的限制:
    必須事先聲明每一個進程請求的最大資源量
    考慮的進程必須無關的,也就是說,它們執行的順序必須沒有任何同步要求的限制
    分配的資源數目必須是固定的。
    在佔有資源時,進程不能退出

三、死鎖檢測與解除 ----- 在檢測到運行系統進入死鎖,進行恢復。

    容許系統進入到死鎖狀態

    死鎖檢測

下圖截自《操做系統--精髓與設計原理》

死鎖的解除
若是利用死鎖檢測算法檢測出系統已經出現了死鎖 ,那麼,此時就須要對系統採起相應的措施。經常使用的解除死鎖的方法:
一、搶佔資源:從一個或多個進程中搶佔足夠數量的資源分配給死鎖進程,以解除死鎖狀態。
二、終止(或撤銷)進程:終止或撤銷系統中的一個或多個死鎖進程,直至打破死鎖狀態。
    a、終止全部的死鎖進程。這種方式簡單粗暴,可是代價很大,頗有可能會致使一些已經運行了好久的進程前功盡棄。
    b、逐個終止進程,直至死鎖狀態解除。該方法的代價也很大,由於每終止一個進程就須要使用死鎖檢測來檢測系統當前是否處於死鎖狀態。另外,每次終止進程的時候終止那個進程呢?每次都應該採用最優策略來選擇一個「代價最小」的進程來解除死鎖狀態。通常根據以下幾個方面來決定終止哪一個進程:
    進程的優先級
    進程已運行時間以及運行完成還須要的時間
    進程已佔用系統資源
    進程運行完成還須要的資源
    終止進程數目
    進程是交互仍是批處理

避免死鎖的方式

  既然可能產生死鎖,那麼接下來,講一下如何避免死鎖。

一、讓程序每次至多隻能得到一個鎖。固然,在多線程環境下,這種狀況一般並不現實

二、設計時考慮清楚鎖的順序,儘可能減小嵌在的加鎖交互數量

三、既然死鎖的產生是兩個線程無限等待對方持有的鎖,那麼只要等待時間有個上限不就行了。固然synchronized不具有這個功能,可是咱們可使用Lock類中的tryLock方法去嘗試獲取鎖,這個方法能夠指定一個超時時限,在等待超過該時限以後變回返回一個失敗信息

相關文章
相關標籤/搜索