• 死锁及预防


    一、死锁产生的四个条件

    死锁(死锁最初概念是在多进程模式下提出的,这里以线程来描述是同一个意思)是多线程并发程序中的一个难题,要产生死锁需要满足下面4个条件:

    1. 有限资源互斥条件:资源只能被线程独占,只有被释放后才能被其它线程占用,如光驱、打印机等,这是有资源本身属性锁决定的。现实生活中独木桥就是一个独占资源。
    2. 已占用资源不可抢占:线程在获得资源使用完毕前,其它申请者无法抢占资源,只能等资源占用者主动释放。
    3. 已占用资源且申请其它互斥资源:线程已占用一个互斥资源,又申请被其它线程占用的互斥资源。
    4. 循环等待:多线程循环等待已被其它线程占用的互斥资源。

    二、死锁的预防

    从破坏死锁产生的四个条件角度考虑:

       〈1〉打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。

       〈2〉打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。    

        〈3〉打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。但是,这种策略也有如下缺点:

    (1)在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测的;

    (2)资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,造成长期占着不用的状况。这显然是一种极大的资源浪费;

    (3)降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了。    

    (4)打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但是也存在以下缺点:

    (1)限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销;

    (2)为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间。

    三、Java死锁范例分析以及避免

    package com.journaldev.threads;
     
    public class ThreadDeadlock {
     
        public static void main(String[] args) throws InterruptedException {
            Object obj1 = new Object();
            Object obj2 = new Object();
            Object obj3 = new Object();
     
            Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
            Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
            Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
     
            t1.start();
            Thread.sleep(5000);
            t2.start();
            Thread.sleep(5000);
            t3.start();
     
        }
     
    }
     
    class SyncThread implements Runnable{
        private Object obj1;
        private Object obj2;
     
        public SyncThread(Object o1, Object o2){
            this.obj1=o1;
            this.obj2=o2;
        }
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name + " acquiring lock on "+obj1);
            synchronized (obj1) {
             System.out.println(name + " acquired lock on "+obj1);
             work();
             System.out.println(name + " acquiring lock on "+obj2);
             synchronized (obj2) {
                System.out.println(name + " acquired lock on "+obj2);
                work();
            }
             System.out.println(name + " released lock on "+obj2);
            }
            System.out.println(name + " released lock on "+obj1);
            System.out.println(name + " finished execution.");
        }
        private void work() {
            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    死锁分析,通过jstack产生的线程快照,可以清晰的发现死锁线程及产生的原因。

    Java编码上避免死锁的方法:

    • 避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。例如,这里是另一个运行中没有嵌套封锁的run()方法,而且程序运行没有死锁局面,运行得很成功。
    • 只对有请求的进行封锁:你应当只想你要运行的资源获取封锁,比如在上述程序中我在封锁的完全的对象资源。但是如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。
    • 避免无限期的等待:如果两个线程正在等待对象结束,无限期的使用线程加入,如果你的线程必须要等待另一个线程的结束,若是等待进程的结束加入最好准备最长时间。
    • 使用lock时要在正确的位置如finally代码块中释放锁。
  • 相关阅读:
    【华磊随笔】一个企业的内部讨论(含高层)“30岁IT人,产出代码还是产出思想”之我见。
    dotNetGrid 演示站点(new)争取做ASP.NET中最好的Ajax开源表格控件
    【华磊随笔】进一步的分析:开发者版本;你属于哪个版本的程序员?
    新增格式化单元格数据功能dotNetFlexGrid1.24,快速的设置您的表格数据的不同样式;现在开始扔掉你的Asp.net GridView吧。(更新FieldFormatorHandle参数)
    【华磊随笔】技术到底值不值钱,到底值多少钱有感于公司新产品的上线
    【华磊原创】某企业前两年的开发人员技术能力评估表(不分语言)相对比较全面也比较细
    【华磊原创】安装程序自动检测安装.Net Framework运行环境(使用InnoSetup)
    mainline.js主线
    JS中的“!!”
    读“周大师代码评审”有感
  • 原文地址:https://www.cnblogs.com/doit8791/p/8960774.html
Copyright © 2020-2023  润新知