死锁

死锁的4个必要条件

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。

  • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有,如图2-15所示。
/**  
* 一个简单的死锁类  
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒  
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒  
* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;  
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;  
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。  
*/    
public class DeadLock implements Runnable {    
    public int flag = 1;    
    //静态对象是类的所有对象共享的    
    private static Object o1 = new Object(), o2 = new Object();    
    @Override    
    public void run() {    
        System.out.println("flag=" + flag);    
        if (flag == 1) {    
            synchronized (o1) {    
                try {    
                    Thread.sleep(500);    
                } catch (Exception e) {    
                    e.printStackTrace();    
                }    
                synchronized (o2) {    
                    System.out.println("1");    
                }    
            }    
        }    
        if (flag == 0) {    
            synchronized (o2) {    
                try {    
                    Thread.sleep(500);    
                } catch (Exception e) {    
                    e.printStackTrace();    
                }    
                synchronized (o1) {    
                    System.out.println("0");    
                }    
            }    
        }    
    }    
    
    public static void main(String[] args) {    
            
        DeadLock td1 = new DeadLock();    
        DeadLock td2 = new DeadLock();    
        td1.flag = 1;    
        td2.flag = 0;    
        //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。    
        //td2的run()可能在td1的run()之前运行    
        new Thread(td1).start();    
        new Thread(td2).start();    
    
    }    
}    

如何避免死锁

在有些情况下死锁是可以避免的。三种用于避免死锁的技术:

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时-限则放弃对该锁的请求,并释放自己占有的锁)
  • 死锁检测
    死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。
    每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。
    当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7;线程B拥有锁7,请求锁1)。
    当然,死锁一般要比两个线程互相持有对方的锁这种情况要复杂的多。线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D又在等待线程A。线程A为了检测死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这时它就知道发生了死锁。
    下面是一幅关于四个线程(A,B,C和D)之间锁占有和请求的关系图。像这样的数据结构就可以被用来检测死锁。

一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。

一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1、竞态条件: 定义:竞态条件指的是一种特殊的情况,在这种情况下各个执行单元以一种没有逻辑的顺序执行动作,从而导致...
    Hughman阅读 5,126评论 0 7
  • 死锁产生的原因和解锁的方法 产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) ...
    憩在河岸上的鱼丶阅读 5,330评论 0 4
  • 一、什么是死锁 所谓死锁,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。 二、产生死锁的必要条件 互...
    Q南南南Q阅读 4,149评论 0 0
  • 一、死锁的基本概念 1.1 死锁的定义 一组进程中,每个进程都无限等待被该组进程中另一进程所占用的资源,因而永远无...
    yjaal阅读 5,378评论 0 6
  • 自从开始搞微信公众号吧,就觉得自己找到了好玩的东西。可是每天要专门腾出时间来写点东西,其实真的挺不容易的。于是偶尔...
    小狠莉莉阅读 2,695评论 0 2