修复死锁策略
避免策略
银行转账
我们并不在乎获取锁的顺序,所以要避免相反的获取锁的顺序即可避免死锁,将transferMoney()
方法做以下修改
public void transferMoney(int amount) {
int fromHash = from.hashCode();
int toHash = to.hashCode();
if (fromHash > toHash) { //保证互相转账时获取锁的顺序相同
firstFromSecondTo(amount);
} else if (fromHash < toHash) {
firstToSecondFrom(amount);
} else {
synchronized (TransferMoney.class) { //发生hash碰撞时,再次竞争一个全局锁,谁先抢到谁先执行
firstFromSecondTo(amount);
}
}
}
private void firstFromSecondTo(int amount) { //先from后to
synchronized (from) {
int balance = from.getBalance(); //查看余额是否充足
if (balance - amount < 0) {
System.out.println("余额不足,转账失败");
return;
}
if (time > 0) { //设置时间就延时,若设置转账延时就会进入死锁状态
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (to) {
from.setBalance(balance - amount); //转账操作
to.setBalance(to.getBalance() + amount);
System.out.println("从" + from.getName() + "到" + to.getName() + ",成功转账" + amount + "元");
}
}
}
private void firstToSecondFrom(int amount) { //先to后from
synchronized (to) {
int balance = from.getBalance(); //查看余额是否充足
if (balance - amount < 0) {
System.out.println("余额不足,转账失败");
return;
}
if (time > 0) { //设置时间就延时,若设置转账延时就会进入死锁状态
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (from) {
from.setBalance(balance - amount); //转账操作
to.setBalance(to.getBalance() + amount);
System.out.println("从" + from.getName() + "到" + to.getName() + ",成功转账" + amount + "元");
}
}
}
哲学家就餐
- 服务员检查:当哲学家在进行拿筷子前就会检查,当拿起筷子就会陷入死锁状态,就不会拿起筷子,从而避免死锁
- 餐票检查:设置小于筷子数量的餐票,在哲学家进餐之前必须要拿到餐票才能进食,未拿到餐票的哲学家只能等待
- 环路破坏:我们并不在乎获取筷子(锁)的顺序,所以要改变一个哲学家获取筷子(锁)的顺序,将
initPhilosophers()
方法做以下修改
public static Philosopher[] initPhilosophers(Object[] chopsticks) {
Philosopher[] philosophers = new Philosopher[chopsticks.length];
for (int i = 0; i < philosophers.length; i++) { //初始化哲学家
Object leftChopstick = chopsticks[i];
Object rightChopstick = chopsticks[(i + 1) % chopsticks.length]; //使用取余方式取到哲学家两测的筷子
/*下面是修改代码*/
if (i == philosophers.length - 1) { //若是最后一个哲学家就将其拿筷子顺序颠倒
philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
} else {
philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
}
/*上面是修改代码*/
}
return philosophers;
}
检测与恢复策略
一段时间检测是否有死锁发生,如果使用以下两种恢复策略
- 进程或线程终止:逐个将线程终止,直到解除死锁,终止顺序最好采用以下几种规则
- 重要程度:按照线程对于程序的重要性的优先级顺序
- 已占用资源:占用资源越少的线程优先终止
- 已运行时间:运行时间越短的线程优先终止
- 资源抢占:将已发放出去的资源在回收回来,让线程回退几步,来解除死锁,成本较低
- 缺点:可能同一个线程一直被抢占,造成该线程一直无法执行,导致饥饿现象
鸵鸟策略
若发生死锁的概率极低直接忽略,直到死锁发生时,再进行人工修复
实际开发中避免死锁
- 尽量使用同步代码块,可自己指定锁对象
- 尽量降低锁的粒度,使用不同的锁
- 尽量专锁专用,避免不同功能使用同一把锁
- 避免锁的嵌套,一旦获取锁的顺序相反,就会造成死锁
- 放出资源前先查看是否能收回来,比如银行家算法
- 为每个线程起一个有意义的名字,方便debug进行死锁排查
- 使用并发类而不是自己设计锁:
ConcurrentHashMap
、ConcurrentLinkedQueue
、java.util.concurrent.atomic.*
等- 多用并发集合,效率高,比如
ConcurrentHashMap
;少用同步集合,效率低,比如Hashtable
- 原子类不仅简单方便,且效率比Lock更高
- 多用并发集合,效率高,比如
- 设置超时时间:使用
Lock
的tryLock()
方法,若拿不到锁就会等待指定时间,超时后就会返回false
public static void main(String[] args) throws InterruptedException {
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
new Thread(() -> {
try {
while (true) {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获得了lock1");
Thread.sleep((long) (Math.random() * 10)); //随机延时
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获得了lock2");
System.out.println(Thread.currentThread().getName() + "同时获取的了两把锁");
lock1.unlock(); //完成操作后释放同时获取到的两个锁
lock2.unlock();
break;
} else {
System.out.println(Thread.currentThread().getName() + "获取lock2失败");
System.out.println(Thread.currentThread().getName() + "释放lock1,并重新获取");
lock1.unlock(); //无法同时获取两把锁,就释放lock1
}
} else {
System.out.println(Thread.currentThread().getName() + "获取lock1失败");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
while (true) {
if (lock2.tryLock(2, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获得了lock2");
Thread.sleep((long) (Math.random() * 10)); //随机延时
if (lock1.tryLock(2, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获得了lock1");
System.out.println(Thread.currentThread().getName() + "同时获取的了两把锁");
lock1.unlock(); //完成操作后释放同时获取到的两个锁
lock2.unlock();
break;
} else {
System.out.println(Thread.currentThread().getName() + "获取lock1失败");
System.out.println(Thread.currentThread().getName() + "释放lock2,并重新获取");
lock2.unlock(); //无法同时获取两把锁,就释放lock2
}
} else {
System.out.println(Thread.currentThread().getName() + "获取lock1失败");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Comments NOTHING