死鎖的4個必要條件
產(chǎn)生死鎖必須同時滿足以下四個條件音婶,只要其中任一條件不成立,死鎖就不會發(fā)生。
- 互斥條件:進程要求對所分配的資源(如打印機)進行排他性控制得滤,即在一段時間內(nèi)某 資源僅為一個進程所占有。此時若有其他進程請求該資源盒犹,則請求進程只能等待懂更。
- 不剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走急膀,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)沮协。
- 請求和保持條件:進程已經(jīng)保持了至少一個資源,但又提出了新的資源請求卓嫂,而該資源 已被其他進程占有慷暂,此時請求進程被阻塞,但對自己已獲得的資源保持不放命黔。
- 循環(huán)等待條件:存在一種進程資源的循環(huán)等待鏈呜呐,鏈中每一個進程已獲得的資源同時被 鏈中下一個進程所請求就斤。即存在一個處于等待狀態(tài)的進程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1)蘑辑,Pn等待的資源被P0占有洋机,如圖2-15所示。
/**
* 一個簡單的死鎖類
* 當DeadLock類的對象flag==1時(td1)洋魂,先鎖定o1,睡眠500毫秒
* 而td1在睡眠的時候另一個flag==0的對象(td2)線程啟動绷旗,先鎖定o2,睡眠500毫秒
* td1睡眠結(jié)束后需要鎖定o2才能繼續(xù)執(zhí)行,而此時o2已被td2鎖定副砍;
* td2睡眠結(jié)束后需要鎖定o1才能繼續(xù)執(zhí)行衔肢,而此時o1已被td1鎖定;
* td1豁翎、td2相互等待角骤,都需要得到對方鎖定的資源才能繼續(xù)執(zhí)行,從而死鎖心剥。
*/
public class DeadLock implements Runnable {
public int flag = 1;
//靜態(tài)對象是類的所有對象共享的
private static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都處于可執(zhí)行狀態(tài)邦尊,但JVM線程調(diào)度先執(zhí)行哪個線程是不確定的。
//td2的run()可能在td1的run()之前運行
new Thread(td1).start();
new Thread(td2).start();
}
}
如何避免死鎖
在有些情況下死鎖是可以避免的优烧。三種用于避免死鎖的技術(shù):
- 加鎖順序(線程按照一定的順序加鎖)
- 加鎖時限(線程嘗試獲取鎖的時候加上一定的時限蝉揍,超過時-限則放棄對該鎖的請求,并釋放自己占有的鎖)
- 死鎖檢測
死鎖檢測是一個更好的死鎖預(yù)防機制畦娄,它主要是針對那些不可能實現(xiàn)按序加鎖并且鎖超時也不可行的場景又沾。
每當一個線程獲得了鎖,會在線程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map熙卡、graph等等)將其記下杖刷。除此之外,每當有線程請求鎖驳癌,也需要記錄在這個數(shù)據(jù)結(jié)構(gòu)中挺勿。
當一個線程請求鎖失敗時,這個線程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生喂柒。例如不瓶,線程A請求鎖7,但是鎖7這個時候被線程B持有灾杰,這時線程A就可以檢查一下線程B是否已經(jīng)請求了線程A當前所持有的鎖蚊丐。如果線程B確實有這樣的請求,那么就是發(fā)生了死鎖(線程A擁有鎖1艳吠,請求鎖7麦备;線程B擁有鎖7,請求鎖1)。
當然凛篙,死鎖一般要比兩個線程互相持有對方的鎖這種情況要復(fù)雜的多黍匾。線程A等待線程B,線程B等待線程C呛梆,線程C等待線程D锐涯,線程D又在等待線程A。線程A為了檢測死鎖填物,它需要遞進地檢測所有被B請求的鎖纹腌。從線程B所請求的鎖開始,線程A找到了線程C滞磺,然后又找到了線程D升薯,發(fā)現(xiàn)線程D請求的鎖被線程A自己持有著。這時它就知道發(fā)生了死鎖击困。
下面是一幅關(guān)于四個線程(A,B,C和D)之間鎖占有和請求的關(guān)系圖涎劈。像這樣的數(shù)據(jù)結(jié)構(gòu)就可以被用來檢測死鎖。
一個可行的做法是釋放所有鎖阅茶,回退责语,并且等待一段隨機的時間后重試。這個和簡單的加鎖超時類似目派,不一樣的是只有死鎖已經(jīng)發(fā)生了才回退,而不會是因為加鎖的請求超時了胁赢。雖然有回退和等待企蹭,但是如果有大量的線程競爭同一批鎖,它們還是會重復(fù)地死鎖(編者注:原因同超時類似智末,不能從根本上減輕競爭)谅摄。
一個更好的方案是給這些線程設(shè)置優(yōu)先級,讓一個(或幾個)線程回退系馆,剩下的線程就像沒發(fā)生死鎖一樣繼續(xù)保持著它們需要的鎖送漠。如果賦予這些線程的優(yōu)先級是固定不變的,同一批線程總是會擁有更高的優(yōu)先級由蘑。為避免這個問題闽寡,可以在死鎖發(fā)生的時候設(shè)置隨機的優(yōu)先級。