什麼是死鎖
先通過一段產生死鎖的代碼來理解死鎖是怎麼產生的。
/**
* 線程死鎖
*
*/
public class ThreadDeadkockStudy {
// 錢
static Object money = new Object();
// 貨
static Object goods = new Object();
public static void main(String[] args) {
// 賣家
new Thread(new Runnable() {
public void run() {
// 賣家拿着貨
synchronized (goods) {
System.out.println("賣家:先錢!");
try {
// 我看你怎麼說
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 除非你先給錢
synchronized (money) {
System.out.println("賣家:合作愉快");
}
}
}
}).start();
// 買家
new Thread(new Runnable() {
public void run() {
// 買家拿着錢
synchronized (money) {
System.out.println("買家:不行,我先驗驗貨!");
try {
// 我也看你怎麼說
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 除非你先給我驗驗貨
synchronized (goods) {
System.out.println("買家:合作愉快");
}
}
}
}).start();
}
}
最後輸出的結果是這樣的,我們發現一直卡在這裏,沒有繼續往下執行了。
賣家:先錢!
買家:不行,我先驗驗貨!
再用比較通俗的語言去解釋這個現象。
黑社會和商人一起到達了約定的交易地點。
商人:“先錢!”
黑社會:“不行,我先驗驗貨!”
商人看不到黑社會的錢,不願意把手裏的貨交出去;黑社會怕商人給的假貨,也不把錢拿出來,就這樣僵住了。
兩個不同的線程,分別擁有各自的資源,並且都想擁有對方持有的資源,但是誰也不願意先妥協,最後陷入了僵局,也就是我們所說的死鎖。
死鎖觸發條件
我們繼續看下面的幾組代碼,看看爲什麼他們爲什麼代碼類似,但是沒有產生死鎖呢。
賣家強買強賣
// 賣家強買強賣
public static void sellerFast() {
// 賣家
new Thread(new Runnable() {
public void run() {
synchronized (goods) {
System.out.println("賣家:先錢!");
synchronized (money) {
System.out.println("賣家:合作愉快");
}
}
}
}).start();
// 買家
new Thread(new Runnable() {
public void run() {
synchronized (money) {
System.out.println("買家:不行,我先驗驗貨!");
synchronized (goods) {
System.out.println("買家:合作愉快");
}
}
}
}).start();
}
再看看輸出的結果,由於賣家沒有等待買家的迴應,就把買家兜裏的錢給拿走了,並且把貨也硬塞到了自己的兜裏,只能喫啞巴虧了。
賣家:先錢!
賣家:合作愉快
買家:不行,我先驗驗貨!
買家:合作愉快
這個產生的原因是,我們線程初始化和啓動是需要一點時間的,而第一個線程已經執行完了,第二個線程纔剛剛執行到那裏的時候,這個鎖已經被釋放了。
如果兩個線程執行的先後順序對調的情況,我們就可以理解成是:買家看賣家不在店裏,但是看到了自己想買的東西,沒等賣家回來就把錢放在了櫃檯上,並且留下了個字條:我看你不在,我把貨拿走了,錢我放在櫃檯了!
最後他們成功的完成了這筆交易。
- 互斥條件:進程對於所分配到的資源具有排它性,即一個資源只能被一個進程佔用,直到被該進程釋放 。
- 請求和保持條件:一個進程因請求被佔用資源而發生阻塞時,對已獲得的資源保持不放。
- 不剝奪條件:任何一個資源在沒被該進程釋放之前,任何其他進程都無法對他剝奪佔用。
- 循環等待條件:當發生死鎖時,所等待的進程必定會形成一個環路(類似於死循環),造成永久阻塞。
我們這裏沒有滿足第二條,因爲當我們想要取用該資源的時候,資源已經被提早釋放了,所以就沒有造成死鎖。
如何避免死鎖
在生活中,一般造成死鎖都是由於溝通問題導致的,比如跨行轉賬也可能出現死鎖的情況,但是這完全是可以被避免的。
如果沒有中間機構,我們也可以通過自行妥協的方式,規定一個比較合理的順序,來避免死鎖的誕生。
加鎖順序: 當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生。如果能確保所有的線程都是按照相同的順序獲得鎖,那麼死鎖就不會發生。當然這種方式需要你事先知道所有可能會用到的鎖,然而總有些時候是無法預知的。
加鎖時限: 加上一個超時時間,若一個線程沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試。但是如果有非常多的線程同一時間去競爭同一批資源,就算有超時和回退機制,還是可能會導致這些線程重複地嘗試但卻始終得不到鎖。
死鎖檢測: 死鎖檢測即每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(map、graph等等)將其記下。除此之外,每當有線程請求鎖,也需要記錄在這個數據結構中。死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖並且鎖超時也不可行的場景。
參考資料
文章中出現的任何錯誤歡迎指正,共同進步!
最後做個小小廣告,有對WEB開發和網絡安全感興趣的,可以加羣一起學習和交流!
QQ:425343603