Java並發編程之死鎖相關知識整理
一、什麼是死鎖
所謂死鎖是指多個線程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進
二、死鎖產生的條件
以下將介紹死鎖的必要條件,隻要系統發生死鎖,這些條件必然成立,而隻要上述條件之一不滿足,就不會發生死鎖
互斥條件
進程要求對所分配的資源(如打印機〉進行排他性控制,即在一段時間內某資源僅為一個進程所占有。此時若有其他進程請求該資源,則請求進程隻能等待
不可剝奪條件
進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即隻能由獲得該資源的進程自己來釋放(隻能是主動釋放)
請求與保持條件
進程已經保持瞭至少一個資源,但又提出瞭新的資源請求,而該資源已被其他進程占有,此時請求進程被阻塞,但對自己已獲得的資源保持不放
循環等待條件
存在一種進程資源的循環等待鏈,鏈中每一個進程已獲得的資源同時被鏈中下一個進程所請求s即存在一個處於等待狀態的進程集合{PI, P2,…,, pn}
其中Pi等待的資源被P(i+1)占有( i=0,1,… , n-1),n等待的資源被Po占有
但也有可能Pi等待的資源被P(i+1)占有( i=0,1,… , n-1),但可以通過圈外也獲取資源(不死鎖),如圖所示
三、死鎖產生的演示
接下來我們創建示例類,通過不同線程來獲取不同的鎖看看
public class Deadlock implements Runnable { private int flag;//用於區分走向 //對象鎖 static 使不同線程引用的都是同一地址 private static Object obj1 =new Object(); //對象鎖 static 使不同線程引用的都是同一地址 private static Object obj2 =new Object(); public Deadlock(int flag) { this.flag = flag; } public void run(){ if(flag == 1){ synchronized (obj1){ System.out.println(Thread.currentThread().getName () + "獲取Obj1,需要請求Obj2"); try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2){ System.out.println(Thread.currentThread().getName () + "已獲取Obj1、獲取Obj2"); } } }else{ synchronized (obj2){ System.out.println(Thread.currentThread().getName () + "獲取Obj2,需要請求Obj1"); try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1){ System.out.println(Thread.currentThread().getName () + "已獲取Obj2、獲取Obj1"); } } } } }
這時我們創建兩個線程, 執行這兩個obj的鎖,看看是否會產生死鎖
class DeadlockTest { public static void main(String[] args) { Thread thread1 = new Thread(new Deadlock(1),"線程1"); Thread thread2 = new Thread(new Deadlock(2),"線程2"); thread1.start(); thread2.start(); } } //運行結果如下: 線程1獲取Obj1,需要請求Obj2 線程2獲取Obj2,需要請求Obj1
我們發現並沒有已獲取obj1、obj2或者以獲取obj2、獲取obj1 的輸出,因為他們滿足瞭死鎖產生的條件
四、死鎖的預防
預防死鎖是設法至少破壞產生死鎖的四個必要條件之一嚴格的防止死鎖的出現
破壞互斥條件
“互斥”條件是無法破壞的。在死鎖預防裡主要是破壞其他幾個必要條件,而不去涉及破壞“互斥”條件
破壞“占有並等待”條件
破壞“占有並等待”條件,就是在系統中不允許進程在已獲得某種資源的情況下,申請其他資源
即要想出一個辦法,阻止進程在持有資源的同時申請其他資源,有以下思路可提供:
- 方法一:
即創建進程時,要求它申請所需的全部資源,系統或滿足其所有要求,或什麼也不給它
- 方法二:
要求每個進程提出新的資源申請前,釋放它所占有的資源
這樣一個進程在需要資源A時,須先把它先前占有的資源R釋放掉,然後才能提出對A的申請,即使它可能很快又要用到資源R
破壞“不可搶占”條件
破壞“不可搶占”條件就是允許對資源實行搶奪
如果占有某些資源的一個進程進行下一步資源請求被拒絕,則該進程必須釋放它最初占有的資源
,如果有必要,可再次請求這些資源和另外的資源
如果一個進程請求當前被另一個進程占有的一個資源,則操作系統可以搶占另一個進程,要求它釋放資源。隻有在任意兩個進程的優先級都不相同的條件下,方法二才能預防死鎖
破壞“循環等待”條件
破壞“循環等待”條件的一種方法,是將系統中的所有資源統一編號,進程可在任何時刻提出資源申請,但所有申請必須按照資源的編號順序(升序)提出。這樣做就能保證系統不出現死鎖。
五、死鎖的避免
死鎖的語法是是嚴格限制產生死鎖的條件,避免死鎖的方式不嚴格限制,因為即使死鎖的必要條件存在,也不一定發生死鎖。而是讓程序通過算法再滿足條件後避免死鎖
避免方法:有序資源分配算法
該算法實現步驟如下:
- 必須為所有資源統一編號,例如打印機為1、傳真機為2、磁盤為3等
- 同類資源必須一次申請完,例如打印機和傳真機一般為同一個機器必須同時申請
- 不同類資源必須按順序申請
舉例:有兩個進程P1和P2,有兩個資源R1和R2,P1與P2線程、分別請求資源:R1、R2
P1先獲取R1、R2,而P2就請求等待P1釋放,這樣就破壞瞭環路條件,避免瞭死鎖的發生
避免方法:銀行傢算法
銀行傢算法(Banker’s A1gorithm)是一個避免死鎖(Dead1ock)的著名算法,是由艾茲格·迪傑斯特拉在1965年為T.HE系統設計的一種避免死鎖產生的算法
它以銀行借貸系統的分配策略為基礎,判斷並保證系統的安全運行。流程圖如下:
避免方法:順序加鎖
當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生
我們上面的示例代碼就是這樣的情況,線程1請求Obj1、Obj2,線程2請求Obj2、Obj1
而我們如果能夠保證所有的線程都是按照相同的順序獲得鎖,那麼死鎖就不會發生
列如我們線程1請求Obj1、Obj2,線程2請求Obj1、Obj2
按照順序加鎖是一種有效的死鎖預防機制。但是這種方式需要事先知道所有可能會用到的鎖,但總有些時候是無法預知的,所以該種方式隻適合特定場景
避免方法:限時加鎖
限時加鎖是線程在嘗試獲取鎖的時候加一個超時時間,若超過這個時間則放棄對該鎖請求,並回退並釋放所有已經獲得的鎖
,然後等待一段隨機的時間再重試
以下展示瞭兩個線程以不同的順序嘗試獲取相同的兩個鎖,在發生超時後回很並重試
的場景:
//線程 1 鎖定A Thread 1 locks A //線程 2 鎖定B Thread 2 locks B //線程 1 嘗試去鎖定B,但已被鎖定 Thread 1 attempts to lock 8 but is blocked //線程 2 嘗試去鎖定A,但已被鎖定 Thread 2 attempts to lock A but is blocked //線程 1 等待鎖定B的時間超時瞭 Thread 1' s lock attempt on B times out //線程 1 進行回退並釋放鎖定A的資源 Thread 1 backs up and releases A as well //線程 1 等待一段時間再重試獲取 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毫秒進行重試加鎖,因此它可以先成功地獲取到兩個鎖
,這時線程1嘗試獲取鎖A並且處於等待狀態,當線程2結束時,線程1也可以順利的獲得這兩個鎖
這種方式有兩個缺點:
- 當線程數量少時,該種方式可避免死鎖,但
當線程數量過多,這些線程的加鎖時限相同的概率就高很多,可能會導致超時後重試的死循環
- Java中
不能對synchronized同步塊設置超時時間
,你需要創建自定義鎖或使用Java5中 java .util.concurrent包下的工具
到此這篇關於Java並發編程之死鎖相關知識整理的文章就介紹到這瞭,更多相關Java死鎖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!