在【死锁】这一篇文章中,我们学习了死锁相关的理论知识,本篇文章来看看死锁案例——银行转账问题,以银行转账问题来讨论死锁、死锁的定位、死锁的修复。
相互转账要点分析
- 需要
两把锁
- 获取两把锁成功,且余额大于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并发核心知识体系精讲》