zoukankan      html  css  js  c++  java
  • 死锁及预防

    一、死锁产生的四个条件

    死锁(死锁最初概念是在多进程模式下提出的,这里以线程来描述是同一个意思)是多线程并发程序中的一个难题,要产生死锁需要满足下面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代码块中释放锁。
  • 相关阅读:
    LeetCode OJ 112. Path Sum
    LeetCode OJ 226. Invert Binary Tree
    LeetCode OJ 100. Same Tree
    LeetCode OJ 104. Maximum Depth of Binary Tree
    LeetCode OJ 111. Minimum Depth of Binary Tree
    LeetCode OJ 110. Balanced Binary Tree
    apache-jmeter-3.1的简单压力测试使用方法(下载和安装)
    JMeter入门教程
    CentOS6(CentOS7)设置静态IP 并且 能够上网
    分享好文:分享我在阿里8年,是如何一步一步走向架构师的
  • 原文地址:https://www.cnblogs.com/doit8791/p/8960774.html
Copyright © 2011-2022 走看看