文章目录
死锁的特点与修复策略
死锁的特点:
- 不可提前预料
- 蔓延速度快
- 危害非常大
发生死锁后, 要保存案发现场, 可以用java相关的命令, 把整个堆栈信息保存下来, 立刻修复线上服务.
利用保存的堆栈信息, 排查死锁, 修改代码. 重新发版.
修复死锁的三个策略 :
- 避免策略 : 哲学家就餐的换手方案, 转账换序方案
- 检测与恢复策略: 一段时间检测是否有死锁, 如果有就剥夺某一个资源, 来打开死锁
- 鸵鸟策略: 比喻鸵鸟遇到危险把头埋地上而看不到危险, 逃避心理. 含义是如果发生死锁的概率极其的低 , 那么就直接忽略它 , 直到发生死锁的时候再修复. (适用于用户量不大的系统,此种做法不推荐)
避免策略代码演示
死锁避免策略
避免相反的获取锁的顺序:
转账时避免死锁的案例:
不在乎获取锁的顺序. 而是避免相反的获取锁的顺序.
package com.thread.deadlock;
/**
* 类名称:TransferMoney
* 类描述: 转账时遇到死锁的代码演示
*
* @author: https://javaweixin6.blog.csdn.net/
* 创建时间:2020/9/8 19:24
* Version 1.0
*/
public class TransferMoney implements Runnable {
//根据不同的flag, 给不同的人转账
int flag = 1;
static Account a = new Account(500);
static Account b = new Account(500);
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;
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
thread1.start();
thread2.start();
//主线程等待子线程执行完毕
thread1.join();
thread2.join();
System.out.println("a的余额 " + a.balance);
System.out.println("b的余额 " + b.balance);
}
@Override
public void run() {
//flag 是1 则 a 给b钱
if (flag == 1) {
transferMoney(a, b, 200);
}
//flag 是0 则b 给a钱
if (flag == 0) {
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("余额不足, 转账失败" + Thread.currentThread().getName());
return;
}
//余额充足,则进行转账操作. 转账方扣钱, 收款方收钱
from.balance -= amount;
to.balance += amount;
System.out.println("成功转账" + amount + "元 " + Thread.currentThread().getName());
}
}
//获取转出和转入对象的hash值 如果对象不变, 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冲突 ,那么用第三把锁来避免hash冲突
synchronized (lock) {
synchronized (to) {
synchronized (from) {
new Helper().transfer();
}
}
}
}
}
//账户的静态内部类
static class Account {
//余额
int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
主要的代码如下 , 算出转出方和转入方对象的hash值, 根据hash值的大小, 来决定获取锁的顺序,来避免相反的获取锁的顺序.
由于from 和to对象定义的是静态的, 因此对象算出来的hash值是不会变的.
始终保证了不会持有对方的锁资源.
对于两个对象算出来的hash值如果一致, 即发生hash冲突时 , 是加上第三把锁, 来让两个线程随机的竞争, 只有其中一个线程拿到了最外层的锁, 并且执行完了代码后, 才会释放最外层的锁.
在实际开发中, 如果主键是自增的, 那么可以用主键计算hash值来避免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冲突 ,那么用第三把锁来避免hash冲突
synchronized (lock) {
synchronized (to) {
synchronized (from) {
new Helper().transfer();
}
}
}
}
多次运行程序后如下. 没有发生死锁.