一、什麼是死鎖
死鎖是指在一組進程中,每個進程都在等待其他進程釋放資源,而自己卻不釋放已經佔有的資源,導致所有進程都陷入了無限等待的狀態,無法繼續執行。這種情況被稱為死鎖。
Java中,當兩個或多個線程被無限期地阻塞,等待彼此釋放所佔用的資源時會發生死鎖。死鎖通常發生在多個線程同時請求多個共享資源的時候。
二、死鎖的原因
發生死鎖的原因主要是由於多個線程持有彼此需要的資源,又都不願先釋放手中的資源,因此無法繼續執行。具體來說,發生死鎖需要滿足以下四個條件:
- 互斥條件:指進程對資源進行排它性控制,即在一段時間內某個資源只能被一個進程佔有。
- 請求與保持條件:指進程在申請新的資源時,已經持有其他資源,但是申請的新資源被其他進程佔用,此時請求資源的進程會阻塞,但是它還會繼續持有已經擁有的資源。
- 不剝奪條件:指進程所佔用的資源不能被其他進程搶奪,只能由該進程自己釋放。
- 循環等待條件:指有兩個或多個進程組成環路,每個進程都在等待下一個進程所佔用的資源。
三、如何避免死鎖
1. 破壞互斥條件
由於互斥條件是指對共享資源的排它性控制,但是對於允許同時訪問的資源,我們可以將其從互斥資源中剝離出來。比如對於線程共同使用的讀取文件指針,如果所有線程使用同一指針,會出現互斥衝突。此時,我們可以為每個線程分別創建一個讀取文件指針,避免互斥。
2. 破壞請求和保持條件
為了破壞請求和保持條件,我們可以按照順序一次性請求所有需要的資源,不再一邊持有資源一邊請求其他資源,避免阻塞其他線程。如果請求不到所有資源,就釋放已經獲得的資源,重新開始整個請求流程。
3. 破壞不剝奪條件
不剝奪條件是指佔據資源的線程不能被其他線程強行剝奪資源。此時,我們可以對某些資源進行搶佔,強制剝奪已經佔用該資源的線程。
4. 破壞循環等待條件
為了破壞循環等待條件,我們可以使用資源有序性來防止循環等待的出現。給每個資源分配一個全局唯一的編號,每個線程按編號的順序請求資源,釋放資源則按相反的順序進行,這樣就可以消除循環等待的情況。
四、示例代碼
1. 破壞互斥條件
public class NoMutexDeadlock implements Runnable {
private static final Object LOCK1 = new Object();
private static final Object LOCK2 = new Object();
private boolean flag;
public NoMutexDeadlock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK2");
}
}
} else {
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK1");
}
}
}
}
}
在這個例子中,我們定義了兩個鎖對象,LOCK1和LOCK2,並且兩個線程在獲取資源時使用不同的順序,這樣就避免了死鎖的發生。
2. 破壞請求和保持條件
public class NoRequestHoldDeadlock implements Runnable {
private static final Object LOCK1 = new Object();
private static final Object LOCK2 = new Object();
private boolean flag;
public NoRequestHoldDeadlock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK2");
}
}
} else {
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK1");
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK2");
}
}
}
}
}
在這個例子中,在線程獲取資源時,我們一次性請求所有需要的資源,不再一邊持有資源一邊請求其他資源,避免阻塞其他線程。
3. 破壞不剝奪條件
public class NoDeprivationDeadlock implements Runnable {
private static final Object LOCK1 = new Object();
private static final Object LOCK2 = new Object();
private boolean flag;
public NoDeprivationDeadlock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
while (true) {
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK2");
}
}
}
} else {
while (true) {
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + " 獲取到了 LOCK1");
}
}
}
}
}
}
public class NoDeprivationDeadlockDemo {
public static void main(String[] args) {
new Thread(new NoDeprivationDeadlock(true)).start();
new Thread(new NoDeprivationDeadlock(false)).start();
}
}
在這個例子中,我們使用一個while循環,強制讓線程不停地執行,使得可以強制剝奪已經佔用該資源的線程。
4. 破壞循環等待條件
public class NoCircularWaitDeadlock implements Runnable {
private int resources;
private static final Object lock = new Object();
private static int resourceCount = 0;
public NoCircularWaitDeadlock(int resources) {
this.resources = resources;
}
@Override
public void run() {
int waitCount = 0;
while (true) {
synchronized (lock) {
if (resourceCount >= resources) {
return;
}
resourceCount++;
}
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " 獲取到了資源 " + resourceCount);
try {
if (++waitCount > 100) {
System.out.println(Thread.currentThread().getName() + " 超過等待時間,退出請求");
return;
}
wait(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class NoCircularWaitDeadlockDemo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new NoCircularWaitDeadlock(5)).start();
}
}
}
在這個例子中,我們為每個資源分配一個全局唯一的編號,並且每個線程按編號的順序請求資源,釋放資源則按相反的順序進行,這樣就可以消除循環等待的情況。
五、總結
死鎖問題需要我們從多個方面來解決,可以根據實際情況選擇不同的方法來避免死鎖的出現。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/235878.html
微信掃一掃
支付寶掃一掃