什么时死锁呢?来自百度百科的解释:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
用比较好理解的大白话来说就时线程A拿到了资源1对象的锁,接下来准备要再拿资源2对象的锁的时候,线程B来了,线程B先拿了资源2对象的锁,如何要准备去拿资源1对象的锁。这个时候就进入了一个谁也不让步谁有不能继续执行下去的尴尬场面。再有更多的线程也时同理
下面用一个java小程序来模拟一下死锁的产生:
使用sleep是为了放大暴露出这个问题
public class DeadLock {
public static String resource1 = "resource1";
public static String resource2 = "resource2";
public static void main(String[] args) {
Thread thread1 = new Thread(new BusinessA());
Thread thread2 = new Thread(new BusinessB());
thread1.start();
thread2.start();
}
static class BusinessA implements Runnable {
@Override
public void run() {
try{
System.out.println("BusinessA启动");
while(true){
synchronized(DeadLock.resource1){
System.out.println("BusinessA拿到了resource1的锁");
Thread.sleep(3000);//获取resource1后先等一会儿,让BusinessB有足够的时间锁住resource2
System.out.println("BusinessA想拿resource2的锁。。。。");
synchronized(DeadLock.resource2){
System.out.println("BusinessA获得到了resource2的锁");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
static class BusinessB implements Runnable {
@Override
public void run(){
try{
System.out.println("BusinessB启动");
while(true){
synchronized(DeadLock.resource2){
System.out.println("BusinessB拿得到了resource2的锁");
Thread.sleep(3000);//获取resource2后先等一会儿,让BusinessA有足够的时间锁住resource1
System.out.println("BusinessB想拿resource1的锁。。。。");
synchronized(DeadLock.resource1){
System.out.println("BusinessB获得到了resource1的锁");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
执行结果:
BusinessA启动
BusinessB启动
BusinessA拿到了resource1的锁
BusinessB拿得到了resource2的锁
BusinessA想拿resource2的锁。。。。
BusinessB想拿resource1的锁。。。。
程序陷入死锁,没法往下继续执行了。
产生条件
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件
1
)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2
)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3
)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4
)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
避免出现死锁
理论上是只要打破四个必要条件之一就能有效预防死锁的发生
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 破坏“循环等待”条件:将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。
写程序时应该尽量避免同时获得多个锁,如果一定有必要这么做,则有一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。比如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3,那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定一个先后顺序比较困难,则应pthread_mutex_trylock调用代替pthread_mutex_lock 调用,以免死锁。
银行家算法