死锁造成的原因
有线程T1 和线程T2 ,T1占用资源A也就是锁住了A对象synchronized(A),T2占用资源B也就是锁住了资源B对象syncrhonized。此时T1线程在去申请获取B对象的时候,T2线程还没有释放资源。T1线程就会一直等待资源B,并且也不会释放资源A.而T2也去申请资源A,就会等待资源A被释放,并且也不会释放资源B. 因此就会形成死锁。
class Account {
private int balance;
// 转账
void transfer(Account target, int amt){
// 锁定转出账户
synchronized(this){ ①
// 锁定转入账户
synchronized(target){ ②
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
上面的这些代码就会造成死锁。
解决死锁的办法
- 互斥,共享资源 X 和 Y 只能被一个线程占用;
- 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源Y人时候,不释放共享资源X;
- 不可抢占,其他线程不能强行抢占线程T1占有的资源;
- 循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。
1.一次性申请多个资源
我们可以创建一个Allocator类,用这个类去申请资源。但是这个类必须是单例的。这样我们就可以避免死锁。
class Allocator {
private Allocator(){};
public static Allocator allocator = new Allocator;
public static Allocator getAllocator(){
return allocator;
}
private List<Object> als =
new ArrayList<>();
// 一次性申请所有资源
synchronized boolean apply(
Object from, Object to){
if(als.contains(from) ||
als.contains(to)){
return false;
} else {
als.add(from);
als.add(to);
}
return true;
}
// 归还资源
synchronized void free(
Object from, Object to){
als.remove(from);
als.remove(to);
}
}
class Account {
// actr 应该为单例
Allocator actr =getAllocator();
private int balance;
// 转账
void transfer(Account target, int amt){
// 一次性申请转出账户和转入账户,直到成功
while(!actr.apply(this, target))
;
try{
// 锁定转出账户
synchronized(this){
// 锁定转入账户
synchronized(target){
if (this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
} finally {
actr.free(this, target)
}
}
}
2. 破坏不可抢占条件。
当线程T1无法获取全部资源的时候,就把自己的资源也释放掉。但是这里就无法使用synchronized这个关键字来做了,需要使用Lock关键字来做了。
3. 破坏循环等待条件
我们在加锁的时候,永远让锁资源按照一定的顺序。
class Account {
private int id;
private int balance;
// 转账
void transfer(Account target, int amt){
Account left = this ①
Account right = target; ②
if (this.id > target.id) { ③
left = target; ④
right = this; ⑤
} ⑥
// 锁定序号小的账户
synchronized(left){
// 锁定序号大的账户
synchronized(right){
if (this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
只要做到以上这三点的其中一个就不会出现死锁了。