用代码角度说死锁的解决方案
1 死锁问题
1.1 什么是死锁
线程A,占有资源A,并且等待占有资源B。
线程B,占有资源B,并且等待占有资源A。
1.2 造成死锁的原因
- 互斥
- 共享的资源,只能够被一个线程占用。
- 共享资源只能一对一
- 占有且等待
- 线程A,占有资源A,等待资源B时,不会释放资源A。
- 占着茅坑不拉屎。
- 不可抢占
- 其他线程,不能够抢占别的线程的资源。
- 互相谦让,看上去和谐,实际上却解决不了问题。
- 循环等待
- 线程们一旦进入等待状态,就会一直等下去。
- 死脑筋,不懂得变通。
1.3 从代码说是如何避免死锁的
一旦发生死锁,没什么好办法,只能重启。
所以应该从死锁产生的原因入手,解决死锁产生的原因,避免死锁。
1.3.1 互斥
无法改变。这是共享资源定义的客观事实。
1.3.2 破坏占有且等待
如果可以一次性申请所有的资源,就可以避免等待。
以两个账户AB转账为例子。
public class DeadLock{
public static void main(String[] args){
Account a = new Accout();
Account b = new Accout();
// a向b转100,b向a转100
a.transfer(b, 100);
b.transfer(a, 100);
}
}
// 一次性申请所有的资源
static class Allocator{
private List<Object> als = new ArrayList<Object>;
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 clean(Object from, Object to){
als.remove(from);
als.remove(to);
}
}
static class Accout{
// 单例设计模式
private Allocator actr = Allocator.getInstance();
private int balance;
void transfer(Account target, int amt){
while(!actr.apply(this, target));
try{
// using synchronized 一口气获得所有的资源。
synchronized(this){
sout(this.toString() + " lock obj1");
synchronized(target){
sout(this.toString() + " lock obj2");
if(this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
} finally{
actr.clean(this, target);
}
}
}
private void Allocator(){};
private static class singleTonHoler{
private static Allocator INSTANCE = new Allocator();
}
public static Allocator getInstanece(){
return SingleTonHoler.INSTANCE;
}
1.3.3 破坏不可抢占条件
换句话说,当一个线程发现自己要死锁了,就应该把自己占有的资源释放出去。
synchronized做不到。当synchronized申请不到资源时,线程就直接进入阻塞状态,自然无法释放已经占有资源。
JDK中的java.util.concurrent提供了Lock来解决这个问题。
显式使用Lock类中的定时tryLock功能来替代内置锁机制,可以检测死锁,并且从死锁状态中恢复过来。
使用内置锁的线程获取不到锁会被阻塞,而显式锁可以指定一个个超时时间(TimeOut),在等待超过这个时间后,tryLock就会返回一个失败信息,并且释放其拥有的资源。
public class DeadLock{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args){
Thread a = new Thread(new Lock1());
Thread b = new Thread(new Lock2());
a.start();
b.start();
}
static class Lock1 implements Runnable{
@Override
public void run(){
try{
sout("lock1 running");
while(true){
if(lock1.tryLock(1, TimeUnit.MILLISECONDS)){
sout("Lock1 lock obj1");
if(lock2.tryLock(1, TimeUnit.MILLISECONDS)){
sout("Lock1 lock obj2");
}
}
}
} catch (Exception e){
e.printStackTrace;
} finally {
lock1.unlock();
lock2.unlock();
}
}
}
}
1.3.4 破坏循环等待条件
思路是:对系统中的资源进行统一编号,进程可以在任何时刻提出资源申请,必须按照资源的编号顺序提出。这样做就能保证系统不出现死锁。这就是资源有序分配法。
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;
}
}
}
}
}