面试中经常会被问到死锁,今天就来整理一下死锁的相关知识。
首先看一下死锁的产生,死锁产生的情况有很多种,例如进程推进顺序不当产生死锁、PV操作使用不当产生死锁、资源分配不当引起死锁、对临时性资源使用不加限制引起死锁等。
那么,到底什么是死锁?死锁是指2个及2个以上的进程在执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都无法推进下去,这些永远在互相等待的进程称为死锁进程。
死锁不仅与系统拥有的资源数量有关,而且与资源分配策略,进程对资源的使用要求以及并发进程的推进顺序有关,如果面试中面试官让写一个死锁的案例,最简单的方式就是在同步代码块中嵌套另外一个同步代码块,两个同步代码块使用2个不同的锁。例如:
public class DeadLock {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args){
Thread a = new Thread(new Lock1());
Thread b = new Thread(new Lock2());
a.start();
b.start();
}
}
class Lock1 implements Runnable{
@Override
public void run(){
try{
System.out.println("Lock1 running");
while(true){
synchronized(DeadLock.obj1){
System.out.println("Lock1 lock obj1");
Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
synchronized(DeadLock.obj2){
System.out.println("Lock1 lock obj2");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
class Lock2 implements Runnable{
@Override
public void run(){
try{
System.out.println("Lock2 running");
while(true){
synchronized(DeadLock.obj2){
System.out.println("Lock2 lock obj2");
Thread.sleep(3000);
synchronized(DeadLock.obj1){
System.out.println("Lock2 lock obj1");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
例子中Lock1先锁obj1,Lock2先锁obj2,Lock1再去锁obj2,但是obj2已经被Lock2锁住了,因此锁住失败,同理Lock2也没法锁住obj1,死锁就发生了。
系统形成死锁有4个必要条件:
*互斥条件(mutual exclusion):系统中存在临界资源,进程应互斥地使用这些资源
*占有和等待条件(hold and wait):进程请求资源得不到满足而等待时,不释放已占有的资源
*不剥夺条件(no preemption):已被占用的资源只能由属主释放,不允许被其它进程剥夺
*循环等待条件(circular wait):存在循环等待链,其中,每个进程都在链中等待下一个进程所持有的资源,造成这组进程永远等待
要防止死锁,只需要破坏4个必要条件之一即可满足。破坏第一个条件,使资源可同时访问而不是互斥使用,但许多资源如可写文件、磁带机等具有天生的互斥性,不能被同时访问,因此有的场合不可行。破坏第二个条件,静态分配,让进程在执行中不再申请资源,就不会出现占有某些资源再等待另一些资源的情况,但会严重降低资源利用率。破坏第三个条件,采用剥夺式调度方法,当进程在申请资源未获准许的情况下,如主动释放资源,再去等待。比较好的方法是采用层次分配策略(破坏条件2和条件4),把资源分成多个层次,当进程得到某一层的一个资源后,只能再申请较高层次的资源。当进程是释放某层的一个资源时,必须先释放占有的较高层次的资源,当进程得到某一层的一个资源后,它想申请该层的另一个资源时,必须先释放该层中的已占资源。