一、死鎖的定義
在多線程環境下,當兩個或多個線程都在等待對方執行完畢後才開始執行自己的代碼時,就會發生死鎖。這演變成了一種互相等待的情況,在這種情況下,沒有任何線程能繼續執行。
死鎖是非常危險的程序行為,通常需要重新啟動應用程序才能解決,因此必須避免發生死鎖的情況。
二、死鎖的例子
下面是一個簡單的示例,其中兩個線程試圖獲取彼此持有的鎖,然後相互等待的情況。
public class DeadLockDemo { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void execute() { Thread t1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread 1 acquired lock1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("Thread 1 acquired lock2"); } } }); Thread t2 = new Thread(() -> { synchronized (lock2) { System.out.println("Thread 2 acquired lock2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("Thread 2 acquired lock1"); } } }); t1.start(); t2.start(); } }
在這個示例代碼中,線程1獲取lock1鎖並等待1秒鐘後,試圖獲取lock2鎖;線程2獲取lock2鎖並等待1秒鐘後,試圖獲取lock1鎖。由於它們都試圖獲取彼此持有的鎖,所以這兩個線程都被阻塞。
三、解決死鎖的方案
1. 避免嵌套鎖
一旦有線程持有了一個鎖,其他線程就不能訪問這個鎖。因此,如果兩個線程都在等待對方釋放自己持有的鎖,那麼他們將永遠無法運行。為了避免死鎖,最好避免嵌套鎖。
public class DeadLockDemo1 { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void execute() { Thread t1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread 1 acquired lock1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (lock2) { System.out.println("Thread 2 acquired lock2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t2.start(); } }
在示例代碼中,線程1獲取lock1鎖,但僅僅釋放該鎖,不試圖獲取lock2鎖。線程2也是一樣。因此這兩個線程將不會發生死鎖。
2. 避免循環等待
另一個避免死鎖的方法是避免循環等待。為了達到這個目的,可以對所有鎖進行排序,並按照相同的順序來獲得它們。這可以確保死鎖不會發生。
public class DeadLockDemo2 { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void execute() { Thread t1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread 1 acquired lock1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("Thread 1 acquired lock2"); } } }); Thread t2 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread 2 acquired lock1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("Thread 2 acquired lock2"); } } }); t1.start(); t2.start(); } }
在這個示例代碼中,線程1獲取lock1鎖之後,獲得lock2鎖。線程2獲取lock1鎖前,線程1必須已經釋放了lock1鎖。
3. 使用超時
在一些特定情況下,死鎖是難以避免的。在這種情況下,可以使用超時策略來避免死鎖。當獲取一個鎖時,可以設置一個超時時間,然後在超過指定時間後拋出異常。
public class DeadLockDemo3 { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void execute() { Thread t1 = new Thread(() -> { try { synchronized (lock1) { System.out.println("Thread 1 acquired lock1"); Thread.sleep(1000); synchronized (lock2) { System.out.println("Thread 1 acquired lock2"); } } } catch (InterruptedException e) { throw new IllegalStateException(e); } }); Thread t2 = new Thread(() -> { try { synchronized (lock1) { System.out.println("Thread 2 acquired lock1"); Thread.sleep(1000); synchronized (lock2) { System.out.println("Thread 2 acquired lock2"); } } } catch (InterruptedException e) { throw new IllegalStateException(e); } }); t1.start(); t2.start(); } }
在示例代碼中,線程1和線程2都試圖獲取lock1鎖,然後等待1秒鐘,然後試圖獲取lock2鎖。由於這些鎖有可能處於循環等待狀態,因此這裡使用了鎖超時策略。
四、總結
死鎖是程序中一種不可避免的情況,但是可以通過使用上述的避免方案來減少它們的發生。最好的方法是避免嵌套鎖和循環等待,如果這些避免方法不適用,那麼超時策略也可以實現死鎖的防治。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/150527.html