官术网_书友最值得收藏!

1.9 線程死鎖

1.9.1 什么是線程死鎖

死鎖是指兩個或兩個以上的線程在執行過程中,因爭奪資源而造成的互相等待的現象,在無外力作用的情況下,這些線程會一直相互等待而無法繼續運行下去,如圖1-2所示。

圖1-2

在圖1-2中,線程A已經持有了資源2,它同時還想申請資源1,線程B已經持有了資源1,它同時還想申請資源2,所以線程1和線程2就因為相互等待對方已經持有的資源,而進入了死鎖狀態。

那么為什么會產生死鎖呢?學過操作系統的朋友應該都知道,死鎖的產生必須具備以下四個條件。

● 互斥條件:指線程對已經獲取到的資源進行排它性使用,即該資源同時只由一個線程占用。如果此時還有其他線程請求獲取該資源,則請求者只能等待,直至占有資源的線程釋放該資源。

● 請求并持有條件:指一個線程已經持有了至少一個資源,但又提出了新的資源請求,而新資源已被其他線程占有,所以當前線程會被阻塞,但阻塞的同時并不釋放自己已經獲取的資源。

● 不可剝奪條件:指線程獲取到的資源在自己使用完之前不能被其他線程搶占,只有在自己使用完畢后才由自己釋放該資源。

● 環路等待條件:指在發生死鎖時,必然存在一個線程—資源的環形鏈,即線程集合{T0, T1, T2, …, Tn}中的T0正在等待一個T1占用的資源,T1正在等待T2占用的資源,……Tn正在等待已被T0占用的資源。

下面通過一個例子來說明線程死鎖。

public class DeadLockTest2 {
    // 創建資源
    private static Object resourceA = new Object();
    private static Object resourceB = new Object();
    public static void main(String[] args) {
        // 創建線程A
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread() + " get ResourceA");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get sourceB");
                    synchronized (resourceB) {
                        System.out.println(Thread.currentThread() + "get esourceB");
                    }
                }
            }
        });
        // 創建線程B
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread() + " get ResourceB");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get esourceA");
                    synchronized (resourceA) {
                        System.out.println(Thread.currentThread() + "get ResourceA");
                    }
                };
            }
        });
        // 啟動線程
        threadA.start();
        threadB.start();
    }
}

輸出結果如下。

下面分析代碼和結果:Thread-0是線程A, Thread-1是線程B,代碼首先創建了兩個資源,并創建了兩個線程。從輸出結果可以知道,線程調度器先調度了線程A,也就是把CPU資源分配給了線程A,線程A使用synchronized(resourceA)方法獲取到了resourceA的監視器鎖,然后調用sleep函數休眠1s,休眠1s是為了保證線程A在獲取resourceB對應的鎖前讓線程B搶占到CPU,獲取到資源resourceB上的鎖。線程A調用sleep方法后線程B會執行synchronized(resourceB)方法,這代表線程B獲取到了resourceB對象的監視器鎖資源,然后調用sleep函數休眠1s。好了,到了這里線程A獲取到了resourceA資源,線程B獲取到了resourceB資源。線程A休眠結束后會企圖獲取resourceB資源,而resourceB資源被線程B所持有,所以線程A會被阻塞而等待。而同時線程B休眠結束后會企圖獲取resourceA資源,而resourceA資源已經被線程A持有,所以線程A和線程B就陷入了相互等待的狀態,也就產生了死鎖。下面談談本例是如何滿足死鎖的四個條件的。

首先,resourceA和resourceB都是互斥資源,當線程A調用synchronized(resourceA)方法獲取到resourceA上的監視器鎖并釋放前,線程B再調用synchronized(resourceA)方法嘗試獲取該資源會被阻塞,只有線程A主動釋放該鎖,線程B才能獲得,這滿足了資源互斥條件。

線程A首先通過synchronized(resourceA)方法獲取到resourceA上的監視器鎖資源,然后通過synchronized(resourceB)方法等待獲取resourceB上的監視器鎖資源,這就構成了請求并持有條件。

線程A在獲取resourceA上的監視器鎖資源后,該資源不會被線程B掠奪走,只有線程A自己主動釋放resourceA資源時,它才會放棄對該資源的持有權,這構成了資源的不可剝奪條件。

線程A持有objectA資源并等待獲取objectB資源,而線程B持有objectB資源并等待objectA資源,這構成了環路等待條件。所以線程A和線程B就進入了死鎖狀態。

1.9.2 如何避免線程死鎖

要想避免死鎖,只需要破壞掉至少一個構造死鎖的必要條件即可,但是學過操作系統的讀者應該都知道,目前只有請求并持有和環路等待條件是可以被破壞的。

造成死鎖的原因其實和申請資源的順序有很大關系,使用資源申請的有序性原則就可以避免死鎖,那么什么是資源申請的有序性呢?我們對上面線程B的代碼進行如下修改。

    // 創建線程B
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread() + " get ResourceB");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get ResourceA");
                    synchronized (resourceB) {
                        System.out.println(Thread.currentThread() + "get ResourceA");
                    }
                };
            }
        });

輸出結果如下。

如上代碼讓在線程B中獲取資源的順序和在線程A中獲取資源的順序保持一致,其實資源分配有序性就是指,假如線程A和線程B都需要資源1,2,3, ..., n時,對資源進行排序,線程A和線程B只有在獲取了資源n-1時才能去獲取資源n

我們可以簡單分析一下為何資源的有序分配會避免死鎖,比如上面的代碼,假如線程A和線程B同時執行到了synchronized(resourceA),只有一個線程可以獲取到resourceA上的監視器鎖,假如線程A獲取到了,那么線程B就會被阻塞而不會再去獲取資源B,線程A獲取到resourceA的監視器鎖后會去申請resourceB的監視器鎖資源,這時候線程A是可以獲取到的,線程A獲取到resourceB資源并使用后會放棄對資源resourceB的持有,然后再釋放對resourceA的持有,釋放resourceA后線程B才會被從阻塞狀態變為激活狀態。所以資源的有序性破壞了資源的請求并持有條件和環路等待條件,因此避免了死鎖。

主站蜘蛛池模板: 荆州市| 博罗县| 黑河市| 乌鲁木齐市| 千阳县| 平塘县| 河津市| 两当县| 平利县| 泾阳县| 城口县| 威海市| 资源县| 肇庆市| 彰化县| 分宜县| 竹山县| 昌都县| 高雄县| 杨浦区| 水城县| 巍山| 汉沽区| 固安县| 宁波市| 屯留县| 大理市| 遂宁市| 新干县| 搜索| 无棣县| 肇东市| 留坝县| 米易县| 宁乡县| 孝义市| 大埔区| 沁水县| 宁阳县| 台中市| 深州市|