首先回顧我們對死鎖的定義,通俗易懂地說就是雙方在佔有自己手上的資源時,需要對方手上的資源才能繼續下去,但是雙方又不願意主動放棄自己手上的資源
用一個生活中通俗易懂的例子就是:對方道歉我就道歉
這個模型用程式碼實現最簡單的框架是這樣
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
解決思路:
- 使用主鍵或者雜湊,確保每次轉賬嘗試獲取鎖時,嘗試獲取的都是主鍵或者雜湊值最小的那個鎖
引用:銀行轉賬問題(死鎖)