死鎖-舉例

YaosGHC發表於2024-07-24

首先回顧我們對死鎖的定義,通俗易懂地說就是雙方在佔有自己手上的資源時,需要對方手上的資源才能繼續下去,但是雙方又不願意主動放棄自己手上的資源

用一個生活中通俗易懂的例子就是:對方道歉我就道歉

這個模型用程式碼實現最簡單的框架是這樣

public class MustDeadLock implements Runnable{

    public int flag;
    static final Object o1 = new Object();
    static final Object o2 = new Object();

    @Override
    public void run() {
        System.out.println("執行緒"+Thread.currentThread().getName() + "的flag為" + flag);

        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("執行緒1獲得了兩把鎖");
                }
            }
        }

        if (flag == 2) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("執行緒2獲得了兩把鎖");
                }
            }
        }
    }

    public static void main(String[] args) {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 2;
        Thread t1 = new Thread(r1, "t1");
        Thread t2 = new Thread(r2, "t2");
        t1.start();
        t2.start();
    }
}

然後既然是舉例我們需要對這個模型新增一點實際的情景,比如轉賬

public class TransferMoney implements Runnable {

    static class Account {
        int balance;

        public Account(int balance) {
            this.balance = balance;
        }
    }

    public int flag;
    static Account account1 = new Account(1000);
    static Account account2 = new Account(500);

    @Override
    public void run() {
        // 這裡其實是兩筆互相轉賬的操作導致的死鎖
        if (flag == 1) transferMoney(account1, account2, 500);
        if (flag == 2) transferMoney(account2, account1, 500);
    }

    private void transferMoney(Account from, Account to, int amount) {
        synchronized (from) {
            System.out.println(Thread.currentThread().getName() + "獲取到第一把鎖");
            // 確保兩把鎖分別被不同的執行緒先拿到,發生死鎖
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (to) {
                System.out.println(Thread.currentThread().getName() + "獲取到第二把鎖");
            }
            if (from.balance - amount < 0) {
                System.out.println("餘額不足,轉賬失敗。");
                return;
            }
            from.balance -= amount;
            to.balance += amount;
            System.out.println("成功轉賬" + amount + "元");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 2;
        Thread t1 = new Thread(r1, "第一筆轉賬");
        Thread t2 = new Thread(r2, "第二筆轉賬");
        t1.start();
        t2.start();
        // 這裡的兩個join操作是為了保證下面列印餘額前兩筆轉賬完成
        t1.join();
        t2.join();
        System.out.println("賬戶1的餘額為:" + account1.balance);
        System.out.println("賬戶2的餘額為:" + account2.balance);
    }
}

死鎖檢測與避免

# 獲取到Java程序的 pid
jps
# 檢查執行緒死鎖情況,以及各個執行緒具體持有的鎖情況
jstack 8359

解決思路:

  1. 使用主鍵或者雜湊,確保每次轉賬嘗試獲取鎖時,嘗試獲取的都是主鍵或者雜湊值最小的那個鎖

引用:銀行轉賬問題(死鎖)

相關文章