一、什麼是死鎖
死鎖是指在一組進程中,每個進程都在等待其他進程釋放資源,而自己卻不釋放已經佔有的資源,導致所有進程都陷入了無限等待的狀態,無法繼續執行。這種情況被稱為死鎖。
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