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


1、死鎖是什么?

死鎖是指兩個或多個事務在同一資源上相互占用,並請求鎖定對方的資源,從而導致惡性循環的現象。

當多個進程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進,這種情況就是死鎖。

很顯然,如果沒有外力的作用,那么死鎖涉及到的各個進程都將永遠處於封鎖狀態。


2、死鎖產生的四個必要條件

(1)互斥條件:進程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某資源僅為一個進程所占有。此時若有其他進程請求該資源,則請求進程只能等待。

(2)不剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能由獲得該資源的進程自己來釋放(只能是主動釋放)。

(3)請求和保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他進程占有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。

(4)循環等待條件:存在一種進程資源的循環等待鏈,鏈中每一個進程已獲得的資源同時被鏈中下一個進程所請求。


3、死鎖的產生原因

(1)競爭資源引起進程死鎖

        當系統中供多個進程共享的資源如打印機、公用隊列等等,其數目不足以滿足諸進程的需要時,會引起諸進程對資源的競爭而產生死鎖。

(2)可剝奪資源和不可剝奪資源

        可剝奪資源,是指某進程在獲得這類資源后,該資源可以再被其他進程或系統剝奪。例如,優先權高的進程可以剝奪優先權低的進程的處理機。又如,內存區可由存儲器管理程序,把一個進程從一個存儲區移到另一個存儲區,此即剝奪了該進程原來占有的存儲區,甚至可將一進程從內存調到外存上,可見,CPU和主存均屬於可剝奪性資源。

        不可剝奪資源,當系統把這類資源分配給某進程后,再不能強行收回,只能在進程用完后自行釋放,如磁帶機、打印機等。

(3)競爭不可剝奪資源

        在系統中所配置的不可剝奪資源,由於它們的數量不能滿足諸進程運行的需要,會使進程在運行過程中,因爭奪這些資源而陷於僵局。

        例如,系統中只有一台打印機R1和一台磁帶機R2,可供進程P1和P2共享。假定PI已占用了打印機R1,P2已占用了磁帶機R2,若P2繼續要求打印機R1,P2將阻塞;P1若又要求磁帶機,P1也將阻塞。

        於是,在P1和P2之間就形成了僵局,兩個進程都在等待對方釋放自己所需要的資源,但是它們又都因不能繼續獲得自己所需要的資源而不能繼續推進,從而也不能釋放自己所占有的資源,以致進入死鎖狀態。

(4)競爭臨時資源

        上面所說的打印機資源屬於可順序重復使用型資源,稱為永久資源。還有一種所謂的臨時資源,這是指由一個進程產生,被另一個進程使用,短時間后便無用的資源,故也稱為消耗性資源,如硬件中斷、信號、消息、緩沖區內的消息等,它也可能引起死鎖。

        例如,SI,S2,S3是臨時性資源,進程P1產生消息S1,又要求從P3接收消息S3;進程P3產生消息S3,又要求從進程P2處接收消息S2;進程P2產生消息S2,又要求從P1處接收產生的消息S1。

        如果消息通信按如下順序進行:

P1: ···Relese(S1);Request(S3); ···

P2: ···Relese(S2);Request(S1); ···

P3: ···Relese(S3);Request(S2); ···

        並不可能發生死鎖。但若改成下述的運行順序:

P1: ···Request(S3);Relese(S1);···

P2: ···Request(S1);Relese(S2); ···

P3: ···Request(S2);Relese(S3); ···

        則可能發生死鎖。


4、常用解決死鎖的方法

(1)如果不同程序會並發存取多個表,盡量約定以相同的順序訪問表,可以大大降低死鎖機會。

(2)在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖產生概率。

(3)對於非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產生的概率。

(4)如果業務處理不好,可以用分布式事務鎖或者使用樂觀鎖。


5、如何確保 N 個線程可以訪問 N 個資源,同時又不導致死鎖?

使用多線程的時候,一種非常簡單的避免死鎖的方式就是:指定獲取鎖的順序,並強制線程按照指定的順序獲取鎖。

因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會出現死鎖了。


6、編寫一個將導致死鎖的Java程序?

import java.util.concurrent.TimeUnit;

public class DeadLockTest {

    public static void main(String[] args) {

        String itemA = "itemA";
        String itemB = "itemB";

        // 死鎖
        new Thread(new MyThread(itemA,itemB),"Thread01").start(); // 持有 A 資源,想拿 B 資源
        new Thread(new MyThread(itemB,itemA),"Thread02").start(); // 持有 B 資源,想拿 A 資源
    }
}

class MyThread implements Runnable {

    private String itemA;
    private String itemB;

    public MyThread(String itemA, String itemB) {
        this.itemA = itemA;
        this.itemB = itemB;
    }

    @Override
    public void run() {
        synchronized (itemA) {
            System.out.println(Thread.currentThread().getName() + " item: " + itemA + " => get " + itemB);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (itemB) {
                System.out.println(Thread.currentThread().getName() + " item: " + itemB + " => get " + itemA);
            }
        }
    }
}

示意圖

image-20210531150526176


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM