死锁
死锁分析
void transfer(Account from, Account to, int amount){
from.setAmount(from.getAmount()-amount);
to.setAmount(to.getAmount()+amount);
}
问题:
比如两个人都从from
给to
转了5块钱,第一个人.getAount()
得到10块钱,还没来得及-amout
,第二个人.getAmount()
得到也是10块钱,然后两个人都减了5块钱
,都认为减完以后是5块钱,就都执行各自的from.setAmout()
,这样账户里还有5块钱,但明明转了两次5块,余额应该为0结果却为5
加锁:
void transfer(Account from, Account to, int amount){
synchronized(from){
synchronized(to){
from.setAmount(from.getAmount()-amount);
to.setAmount(to.getAmount()+amount);
}
}
}
把from
和to
都锁住,这样在第一个人转完之前第二个人是进步去的,就不能拿到,synchronized
锁住的是对象,第一个锁住了from
,第二个锁住了to
。对于同一个账户,同一个from
,第一个人进来以后,第二个人就进不来了。
会发生死锁
- 在任何地方都可以线程切换,甚至在一句语句中间
from.setAmount(from.getAmount()-amount);
中from.getAmount()
可以断开,from.getAmount()-amount
可以断开,rom.setAmount(from.getAmount()-amount);
也可以断开,当加上synchronized
就安全
- 要尽力设想对自己最不利的情况
void transfer(Account from, Account to, int amount){
synchronized(from){
...
}
}
-
synchronized(from)
-> 别的线程在等待from
不利的情况:自己刚锁完,就有其他人等待这个锁
void transfer(Account from, Account to, int amount){
synchronized(from){
synchronized(to){
...
}
}
}
-
synchronized(to)
-> 别的线程已经锁住了to
不利的情况:别人锁了to
,自己再用的时候要等待 -
可能死锁:
transfer(a,b,100)
和transfer(b,a,100)
同时进行
左边抢走了a
的锁,右边抢走了b
的锁
左边要获取的b
锁已经被右边抢走,右边要获取的a
锁已经被左边抢走
死锁条件,必须同时满足
互斥等待:有一段代码块或一个操作,同时只能有一个人做,没抢到锁的人必须等待第一个人做完。就是必须有锁的存在
抢占和等待(hold and wait): 抢到了锁,不做事情,却等待另外一个锁
循环等待:拿了a的锁却等b,另外一个却拿到了b的锁来等a
无法剥夺的等待:一直等待锁
死锁防止:
破除互斥等待 -> 一般无法破除
破除hold and wait -> 一次性获取所有资源
例如
void transfer(Account from, Account to, int amount){
synchronized(from){
synchronized(to){
...
}
}
}
是分开加锁的,如果同时锁住from
和to
就可以,但大部分语言都不支持同时锁两个对象。对代码做修改,让from
和to
暴露锁,锁是针对amount
的,from
可以getAmountLock
, to
也可以getAmountLock
这样就获得了两个锁的object,对锁的object可以用其他方法锁住他们,锁的时候可以带上很短的超时,先把from锁住,to带一个非常短的超时去锁,如果锁不住,就把from释放,过段时间重新尝试将两者都锁住。
破除循环等待 -> 按顺序获取资源
根据AccountID来加锁,谁的AccountID小谁先加锁就不会产生循环等待
破除无法剥夺的等待 ->加入超时