什麼是死鎖?如何解決死鎖?

奈學教育發表於2020-08-11
死鎖是指兩個或兩個以上的程式在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程式稱為死鎖程式。產生死鎖的原因,主要包括:
  • 系統資源不足;
  • 程式執行的順序有問題;
  • 資源分配不當等。
如果系統資源充足,程式的資源請求都能夠得到滿足,那麼死鎖出現的可能性就很低;否則, 就會因爭奪有限的資源而陷入死鎖。其次,程式執行的順序與速度不同,也可能產生死鎖。產生死鎖的四個必要條件:
  • 互斥條件:一個資源每次只能被一個程式使用。
  • 請求與保持條件:一個程式因請求資源而阻塞時,對已獲得的資源保持不放。
  • 不剝奪條件:程式已獲得的資源,在末使用完之前,不能強行剝奪。
  • 迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈等待資源關係。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

如何解決死鎖?

理解了死鎖的原因,尤其是產生死鎖的四個必要條件,我們就可以最大可能地避免、預防和解除死鎖。所以,在系統設計、程式排程等方面注意如何不讓這四個必要條件成立,如何確定資源的合理分配演算法,避免程式永久佔據系統資源,這就是避免、預防和解決死鎖的最佳實踐。此外,也要防止程式在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。

用下面一段死鎖的程式碼來理解吧

public class DeadLock {
    public static final String LOCK_1 = "lock1";
    public static final String LOCK_2 = "lock2";
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            try {
                while (true) {
                    synchronized (DeadLock.LOCK_1) {
                        System.out.println(Thread.currentThread().getName() + " 鎖住 lock1");
                        Thread.sleep(1000);
                        synchronized (DeadLock.LOCK_2) {
                            System.out.println(Thread.currentThread().getName() + " 鎖住 lock2");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        Thread threadB = new Thread(() -> {
            try {
                while (true) {
                    synchronized (DeadLock.LOCK_2) {
                        System.out.println(Thread.currentThread().getName() + " 鎖住 lock2");
                        Thread.sleep(1000);
                        synchronized (DeadLock.LOCK_1) {
                            System.out.println(Thread.currentThread().getName() + " 鎖住 lock1");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        threadA.start();
        threadB.start();
    }
}
如上述程式碼所示,我們啟動了兩個執行緒,在每個執行緒中都要獲得DeadLock.LOCK_1和DeadLock.LOCK_2,其中
  • threadA,先獲取DeadLock.LOCK_1,再獲取DeadLock.LOCK_2
  • threadB,先獲取DeadLock.LOCK_2,再獲取DeadLock.LOCK_1
這樣,當threadA獲取到DeadLock.LOCK_1之後,就要去獲取DeadLock.LOCK_2,而DeadLock.LOCK_2則是先被threadB獲取了,因此threadA就需要等待threadB釋放DeadLock.LOCK_2之後才能繼續執行;但是threadB在獲取到DeadLock.LOCK_2之後,卻是在等待threadA釋放DeadLock.LOCK_1,因此這就形成了“迴圈等待條件”,從而形成了死鎖。想要解決這個死鎖很簡單,我們只需要讓threadA和threadB獲取DeadLock.LOCK_1和DeadLock.LOCK_2的順序相同即可,例如:
public class DeadLock {
    public static final String LOCK_1 = "lock1";
    public static final String LOCK_2 = "lock2";
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            try {
                while (true) {
                    synchronized (DeadLock.LOCK_1) {
                        System.out.println(Thread.currentThread().getName() + " 鎖住 lock1");
                        Thread.sleep(1000);
                        synchronized (DeadLock.LOCK_2) {
                            System.out.println(Thread.currentThread().getName() + " 鎖住 lock2");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        Thread threadB = new Thread(() -> {
            try {
                while (true) {
                    synchronized (DeadLock.LOCK_1) {
                        System.out.println(Thread.currentThread().getName() + " 鎖住 lock1");
                        Thread.sleep(1000);
                        synchronized (DeadLock.LOCK_2) {
                            System.out.println(Thread.currentThread().getName() + " 鎖住 lock2");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        threadA.start();
        threadB.start();
    }
}
除此之外,還有一種解決方法,那就是讓DeadLock.LOCK_1和DeadLock.LOCK_2的值相同,例如:
    public static final String LOCK_1 = "lock";
    public static final String LOCK_2 = "lock";
這是為什麼呢?因為字串有一個常量池,如果不同的執行緒持有的鎖是具有相同字元的字串鎖時,那麼兩個鎖實際上就是同一個鎖。
本文來源於:奈學開發者社群,如有侵權請聯絡我刪除~


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69976011/viewspace-2710964/,如需轉載,請註明出處,否則將追究法律責任。

相關文章