Java死鎖

了解死鎖之前,我們要先了解線程的狀態(tài),或者叫線程的生命周期。

線程的狀態(tài)

線程的狀態(tài)主要分為上圖中的這幾種狀態(tài),這里我們需要注意一下幾點:

  • 初始狀態(tài)
    new出的一個線程對象,注意此時線程并未執(zhí)行,只有調(diào)用start方法后才會執(zhí)行。
  • 運行態(tài)
    Java中規(guī)定將兩種合二為一 操作系統(tǒng)分為兩種:
    • 運行中 -->被Cpu分配了時間片
    • 就緒 -->等待被Cpu分配了時間片
  • 阻塞態(tài)
    有且僅有調(diào)用synchronized關(guān)鍵字且沒有拿到鎖的情況下算阻塞 wait、sleep等不算,算等待或等待超時
    lock顯示鎖 沒有拿到鎖 進(jìn)入等待或等待超時狀態(tài),因為Lock底層調(diào)用的是LockSupport
    阻塞被動進(jìn)入 等待主動進(jìn)入

定義

死鎖是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖。

造成死鎖的原因及解決辦法

造成死鎖需要以下三個條件,下面我們對這三個條件,進(jìn)行詳細(xì)的分析和解釋。

  • 多個操作者 (M>=2) 爭奪多個資源(N>=2) N<=M

為什么需要多個操作者,如果只有一個操作者,還需要加鎖嗎? 很顯然不需要
為什么需要多個資源,如果只有一個資源,誰搶到就是誰的,用完了其他人接著用。
為什么需要N<=M,如果資源數(shù)大于操作者數(shù)量,操作者可以去爭搶沒有被爭搶的資源。

  • 爭奪資源順序不對

public class DieSync {

    private static Object objectA = new Object();
    private static Object objectB = new Object();

    private static void MainDo() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        synchronized (objectA){
            System.out.println(threadName + "get objectA");
            Thread.sleep(100);
            synchronized (objectB){
                System.out.println(threadName + "get objectB");
            }
        }

    }

    private static void SonDo() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        synchronized (objectB){
            System.out.println(threadName + "get objectB");
            Thread.sleep(100);
            synchronized (objectA){
                System.out.println(threadName + "get objectA");
            }
        }
    }

    private static class SonThread extends Thread{
        private String name;

        public SonThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            try {
                SonDo();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setName("主線程");
        SonThread sonThread = new SonThread("子線程");
        sonThread.start();
        MainDo();
    }

}

主線程先拿到了A,然后去拿B,發(fā)現(xiàn)B被子線程拿了,然后就在這阻塞著
而子線程先拿到了B,然后去拿A,這時候發(fā)現(xiàn)A被主線程拿了,然后也阻塞
兩者互不相讓,你等我,我等你,等到天荒地老,??菔癄€
如果我們將代碼改為,主線程和子線程都先拿A在拿B,這個問題就解決了,這也就是解決辦法,代碼就不演示了,大家可以自行嘗試。

  • 拿到資源不放手

這點很好理解,就是說你拿到鎖了,一直不撒手,占著茅坑不出來了,外面的人自然就拉了褲了,哈哈。
我們下面通過段代碼來演示下正確的做法。

public class TryLock {
    private static Lock lockA = new ReentrantLock();
    private static Lock lockB = new ReentrantLock();

    private static void firstToSecond() throws InterruptedException {
        String threadNmae = Thread.currentThread().getName();
        Random random = new Random();
        while (true) {
            if (lockA.tryLock()) {
                try {
                    System.out.println(threadNmae + "get lockA");
                    if (lockB.tryLock()) {
                        try {
                            System.out.println(threadNmae + "get lockB");
                            System.out.println("firstToSecond do work.....");
                            break;
                        } finally {
                            lockB.unlock();
                        }
                    }
                } finally {
                    lockA.unlock();
                }
            }
            Thread.sleep(random.nextInt(3));
        }
    }


    private static void secondToFirst() throws InterruptedException {
        String threadNmae = Thread.currentThread().getName();
        Random random = new Random();
        while (true) {
            if (lockB.tryLock()) {
                try {
                    System.out.println(threadNmae + "get lockB");
                    if (lockA.tryLock()) {
                        try {
                            System.out.println(threadNmae + "get lockA");
                            System.out.println("secondToFirst do work.....");
                            break;
                        } finally {
                            lockA.unlock();
                        }
                    }
                } finally {
                    lockB.unlock();
                }
            }
            Thread.sleep(random.nextInt(3));
        }
    }

    private static class SecondThread extends Thread{
        private String name;

        public SecondThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            try {
                secondToFirst();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setName("主線程");
        SecondThread secondThread = new SecondThread("子線程");
        secondThread.start();
        firstToSecond();
    }
}

這段代碼的邏輯也很簡單,就是將內(nèi)置鎖換成了顯示鎖,顯示鎖(Lock)可以進(jìn)行嘗試拿鎖,中斷拿鎖等操作。先嘗試拿鎖,拿到了繼續(xù)執(zhí)行,沒拿到就繼續(xù)循環(huán),直到拿到為止。
這里要注意以下兩點:

  • 當(dāng)我們使用Lock的時候,一定要記得加try{} finally{}結(jié)構(gòu),在finally中釋放鎖,避免我們代碼拋出異常,導(dǎo)致鎖不釋放,造成死鎖。
  • 我想大家都注意到了這行代碼 Thread.sleep(random.nextInt(3));,這行代碼的作用就是避免一直嘗試拿鎖,造成資源浪費,這種現(xiàn)象也叫活鎖。打印結(jié)果太長了,大家有興趣可以自己運行代碼看一下。

總結(jié)

上面我們說造成死鎖有三個條件,條件1的造成原因是因為我們的業(yè)務(wù)需要,沒有辦法解決,所以我們只能從后兩個條件想辦法,具體的解決辦法,上面代碼也給出了示例,主要方法就是

  • 調(diào)整拿鎖順序
  • 用顯示鎖,嘗試去拿鎖(注意我們上面提到的注意點)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容