什么是死鎖
首先死鎖的產品肯定是發生在兩個或者兩個以上的線程之間,因為線程請求獨占資源時,由于資源被對方占用,自身被掛起等待;從而造成相互等待,在沒有干預時,將會一直等待下去的現象稱為死鎖。
死鎖產生的原因
死鎖的產生都是由于多個線程之間資源競爭造成的,那么需要什么樣的條件,多個線程之間才會造成死鎖的現象呢?下面是造成死鎖產生的四個條件:
1、資源是互斥的;
也就是說線程請求的資源是互斥的,也就是說都是獨占鎖,只要該資源被某個線程持有后,其他線程無法請求到該資源,自能被放入等待隊列,然后線程被掛起;直到持有該資源的線程釋放資源后,被掛起的線程會重新去請求該資源。
2、某線程持有了一個獨占資源,并且還需要請求另一獨占資源;
也就是說某一個線程它已經獲取了一個獨占的資源,持有了一個獨占的鎖,但是由于業務需要,他還需要再請求另一個獨占的資源;如果正好這個資源被別的線程持有,它就只能等待,但是并不會釋放它持有的其他資源;
3、線程持有的資源只能由自己釋放;
也就是說當某線程獲取某獨占資源時,這個資源已經是自己持有的了,當其他線程獲取的時候,只要自己沒有釋放,其他線程是無法獲取的,其他線程也不能主動去釋放當前線程持有的資源;
4、多個線程被掛起等待資源時,造成了循環等待的情況;
也就是說由于某個線程持有了一個資源,這個時候它需要請求另一個資源,另一個資源剛好被另一個線程持有,而另一個線程又需要請求這個資源;比如線程 A,它持有一個資源 A 這個時候由于業務需要,它需要請求資源 B,而資源 B 被線程 B 持有,而線程 B 剛好又需要請求資源 A,于是就造成了循環等待的情況了。
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
Thread threadA = new Thread(() -> {
synchronized (lockA) {
System.out.println("threadA acquired the lockA");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("threadA waiting for lockB");
synchronized (lockB) {
System.out.println("threadA acquired the lockB");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lockB) {
System.out.println("threadB acquired the lockB");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("threadB waiting for lockA");
synchronized (lockA) {
System.out.println("threadB acquired the lockA");
}
}
});
threadA.start();
threadB.start();
}
結果
threadA acquired the lockA
threadB acquired the lockB
threadA waiting for lockB
threadB waiting for lockA
以上示例就產生了死鎖,它達到了以上所說的四個必要的條件。
如何避免死鎖
要想避免死鎖的產生,就需要根據死鎖產生的條件來避免,所以就需要破壞死鎖產生的條件。而從上面的四個條件來說,第一個和第三個資源是不能改變的,我們只能改變第二個和第四個條件,所以有兩種方式。
方式1:
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
Thread threadA = new Thread(() -> {
synchronized (lockA) {
System.out.println("threadA acquired the lockA");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("threadA waiting for lockB");
synchronized (lockB) {
System.out.println("threadA acquired the lockB");
}
});
Thread threadB = new Thread(() -> {
synchronized (lockB) {
System.out.println("threadB acquired the lockB");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("threadB waiting for lockA");
synchronized (lockA) {
System.out.println("threadB acquired the lockA");
}
});
threadA.start();
threadB.start();
}
方式2:
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
Thread threadA = new Thread(() -> {
synchronized (lockA) {
System.out.println("threadA acquired the lockA");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("threadA waiting for lockB");
synchronized (lockB) {
System.out.println("threadA acquired the lockB");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lockA) {
System.out.println("threadB acquired the lockB");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("threadB waiting for lockA");
synchronized (lockB) {
System.out.println("threadB acquired the lockA");
}
}
});
threadA.start();
threadB.start();
}
結果
threadA acquired the lockA
threadA waiting for lockB
threadA acquired the lockB
threadB acquired the lockB
threadB waiting for lockA
threadB acquired the lockA
方式1,破壞了第二個條件;方式2,由于改變了線程 B 加鎖的順序,因此它破壞了第四個條件。
總結
死鎖是在實際工作中很容易產生的問題,所以要清楚死鎖的產生條件,避免在工作中創造產生死鎖的條件,當實際工作有類似的需求時,要破壞死鎖產生的條件,從而避免死鎖。