在【死鎖】這一篇文章中,我們學習了死鎖相關的理論知識,本篇文章來看看死鎖案例——銀行轉賬問題,以銀行轉賬問題來討論死鎖、死鎖的定位、死鎖的修復。
相互轉賬要點分析
- 需要
兩把鎖
- 獲取兩把鎖成功,且餘額大於0,則扣除轉出人,增加收款人的餘額,而且這是個
原子操作
- 獲取鎖的
順序相反
導致死鎖
代碼演示
public class TransferMoney implements Runnable {
private int flag = 1;
private static Account a = new Account(500);
private static Account b = new Account(500);
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
//定義兩個線程,flag = 1和0分別模擬a和b
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("a的餘額" + a.balance);
System.out.println("b的餘額" + b.balance);
}
@Override
public void run() {
if (flag == 1) {
//a轉賬給b
transferMoney(a, b, 200);
}
if (flag == 0) {
//b轉賬給a
transferMoney(b, a, 200);
}
}
/**
* 轉賬方法
*
* @param from 轉出人
* @param to 收款人
* @param amount 轉賬金額
*/
public static void transferMoney(Account from, Account to, int amount) {
synchronized (from) {
try {
//模擬業務耗時
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (to) {
if (from.balance - amount < 0) {
System.out.println("餘額不足,轉賬失敗。");
return;
}
from.balance -= amount;
to.balance = to.balance + amount;
System.out.println("成功轉賬" + amount + "元");
}
}
}
/**
* 收款賬戶
*/
static class Account {
public Account(int balance) {
this.balance = balance;
}
int balance;
}
}
打印結果:啓動程序,發現並未打印結果輸出,而且程序未停止
,這就發生了死鎖。
死鎖定位
-
jstack命令行
未完待續… -
利用工具類:
ThreadMXBean類
,上面死鎖程序的main函數改成如下:
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
//定義兩個線程,flag = 1和0分別模擬a和b
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
//讓主程序睡一會兒,先讓上面的程序運行死鎖之後就能被檢測到
Thread.sleep(1000);
//發現死鎖
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
for (int i = 0; i < deadlockedThreads.length; i++) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
System.out.println("發現死鎖" + threadInfo.getThreadName());
}
}
}
打印結果:
死鎖修復
- 【死鎖】這篇文章提到多種死鎖修復的方案,這裏用到的是
死鎖避免策略
:把獲取兩把鎖的規則改一下,原來的規則是先獲取轉出人的鎖,再獲取收款人的鎖,這就會造成兩個轉出人都在等對方釋放鎖的情況。 - 現在我們把規則改成:
所有的交易都先獲取更小的鎖,獲取到了小的鎖才能獲取大鎖
,這就避免了環形的死鎖,假如說這兩個鎖的大小一樣,這時候就需要一把額外的鎖來進行交易流程的控制,相當於一場“加時賽”。
public class TransferMoney implements Runnable {
private int flag = 1;
private static Account a = new Account(500);
private static Account b = new Account(500);
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
//定義兩個線程,flag = 1和0分別模擬a和b
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("a的餘額" + a.balance);
System.out.println("b的餘額" + b.balance);
}
@Override
public void run() {
if (flag == 1) {
//a轉賬給b
transferMoney(a, b, 200);
}
if (flag == 0) {
//b轉賬給a
transferMoney(b, a, 200);
}
}
/**
* 轉賬方法
*
* @param from 轉出人
* @param to 收款人
* @param amount 轉賬金額
*/
public static void transferMoney(Account from, Account to, int amount) {
/**
* 輔助類
*/
class Helper {
public void transfer() {
if (from.balance - amount < 0) {
System.out.println("餘額不足,轉賬失敗。");
return;
}
from.balance -= amount;
to.balance = to.balance + amount;
System.out.println("成功轉賬" + amount + "元");
}
}
//使用類的hash值來幫助排序
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if (fromHash < toHash) {
synchronized (from) {
synchronized (to) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (to) {
synchronized (from) {
new Helper().transfer();
}
}
} else {
//發生hash碰撞時,可以增加鎖來進行控制,類似“加時賽”
synchronized (lock) {
synchronized (to) {
synchronized (from) {
new Helper().transfer();
}
}
}
}
}
/**
* 收款賬戶
*/
static class Account {
public Account(int balance) {
this.balance = balance;
}
int balance;
}
}
打印結果:
筆記來源:慕課網悟空老師視頻《Java併發核心知識體系精講》