05-如何避免死锁

死锁现象:

  最常见的就是转账操作:B转A的同时,A转账给B,那么先锁B再锁A,但是,另一个线程是先锁A再锁B,然而,如果两个线程同时执行,那么就是出现死锁的情况,线程T1锁了A请求锁B,此时线程T2锁了B请求锁A,都在等着对方释放锁,然而自己都不会释放锁,故死锁。

 

死锁发生的条件:

  •   占用且等待;
  •   已锁定的资源不可抢占;
  •   循环等待

 

避免的方法:
  一、破坏循环等待的条件按序申请资源

  最简单的办法,假设每个账户都有不同的属性id,这个id可以作为排序字段。就是无论哪个线程执行的时候,都按照顺序加锁,即按照A和B的id大小来加锁,这样,无论哪个线程执行的时候,都会先加锁A,再加锁B,A被加锁,则等待释放。另外的线程只能先等A被释放,再申请A的锁,这样就不会被死锁了。

 1 public class Account {
 2     private int id; //每个账户都有不同的属性id,这个 id 可以作为排序字段
 3     private int balance;    //账户余额
 4     /**
 5      * 转账
 6      * @param target 目标账户
 7      * @param amt   转账金额
 8      */
 9     void transfer(Account target, int amt){
10         Account left = this;    //当前账户(转出账户)
11         Account right = target;
12         if (this.id > target.id) {
13             left = target;
14             right = this;
15         }
16         // 锁定序号小的账户
17         synchronized(left){
18             // 锁定序号大的账户
19             synchronized(right){
20                 if (this.balance > amt){
21                     this.balance -= amt;
22                     target.balance += amt;
23                 }
24             }
25         }
26     }
27 }

二、破坏不可抢占条件

  要主动释放锁定的资源,synchronized无法做到。而java.util.concurrent包下的Lock是可以轻松解决这个问题的。

三、破坏占用且等待条件

  要破坏占用且等待条件,可以一次申请所有资源。比如转账操作,可以增加一个账本管理员,柜员要拿到账本只能通过管理员。A转给B,需要柜员(线程)拿到账本A和账本B,如果此时账本管理员手里只有账本B,这个时候是不会给他账本A的,只有同时拥有A和B时,才会给他。

  

 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4     class Allocator {
 5         private List<Object> als = new ArrayList<>();
 6         // 一次性申请所有资源
 7         synchronized boolean apply(Object from, Object to){
 8             if(als.contains(from) || als.contains(to)){
 9                 return false;
10             } else {
11                 als.add(from);
12                 als.add(to);
13             }
14             return true;
15         }
16         // 归还资源
17         synchronized void free(Object from, Object to){
18             als.remove(from);
19             als.remove(to);
20         }
21 
22     class Account1 {
23         // actr 应该为单例
24         private Allocator actr;
25         private int balance;    //账户余额
26         // 转账
27         void transfer(Account1 target, int amt){
28             // 一次性申请转出账户和转入账户,直到成功
29             while(!actr.apply(this, target));
30             try{
31                 // 锁定转出账户
32                 synchronized(this){
33                     // 锁定转入账户
34                     synchronized(target){
35                         if (this.balance > amt){
36                             this.balance -= amt;
37                             target.balance += amt;
38                         }
39                     }
40                 }
41             } finally {
42                 actr.free(this, target);
43             }
44         }
45     }
46 }
View Code

 

posted @ 2019-10-17 15:05  45°仰望星空  阅读(251)  评论(0)    收藏  举报