龙空技术网

死锁产生的原因及其解决方案

面试题解析 468

前言:

现时各位老铁们对“死锁的类型主要有”大概比较看重,小伙伴们都想要知道一些“死锁的类型主要有”的相关知识。那么小编也在网摘上网罗了一些关于“死锁的类型主要有””的相关文章,希望各位老铁们能喜欢,各位老铁们快快来了解一下吧!

【死记硬背】

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁产生的四个必要条件:互斥条件、不可剥夺条件、请求与保持条件、循环等待条件。

死锁的解决方案:调整申请锁的范围、调整申请锁的顺序、不使用显示的去锁,我们用信号量去控制、避免死锁方针。

【答案解析】

实现一段死锁的代码

这个是我【进入阿里面试的一道笔试题】,大家可以记下下面的代码。

public class Demo3 implements Runnable{    private String tag;    private static Object lock1 = new Object();    private static Object lock2 = new Object();    public Demo3(String tag){        this.tag = tag;    }    @Override    public void run() {        if(tag.equals("a")){            synchronized (lock1) {                try {                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                synchronized (lock2) {                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");                }            }        }        if(tag.equals("b")){            synchronized (lock2) {                try {                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                synchronized (lock1) {                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");                }            }        }    }    public static void main(String[] args) {        Demo3 d1 = new Demo3("a");        Demo3 d2 = new Demo3("b");        Thread t1 = new Thread(d1, "t1");        Thread t2 = new Thread(d2, "t2");        t1.start();        t2.start();    }}
四个必要条件解析
#互斥条件一个资源只能被一个进程占用。#不可剥夺条件某个进程占用了资源,就只能他自己去释放。#请求和保持条件某个进程之前申请了资源,我还想再申请资源,之前的资源还是我占用着,别人别想动。除非我自己不想用了,释放掉。#循环等待条件一定会有一个环互相等待。
死锁的解决方案代码演示

调整申请锁的范围实现方案:

public class TestClass {    public void method(TestClass clazz) {        System.out.println("TestClass method in");        synchronized(this){            //do something        }        clazz.method2();        System.out.println("TestClass method out");    }    public synchronized void method2() {        System.out.println("TestClass method2");    }}//上面代码原来锁是加在方法上的,现在改为在方法内的一部分,这样在使用第二个锁时本身的锁已经释放了。如果减小锁的申请范围可以避免锁的申请发生闭环的话,那么就可以避免死锁。

调整申请锁的顺序实现方案:

public class Account {    private int id;    // 主键    private String name;    private double balance;    public void transfer(Account from, Account to, double money){        if(from.getId() > to.getId()){            synchronized(from){                synchronized(to){                    // transfer                }            }        }else{            synchronized(to){                synchronized(from){                    // transfer                }            }        }    }    public int getId() {        return id;    }}//在有些情况下是不允许我们调整锁的范围的,比如银行转账的场景下,我们必须同时获得两个账户上的锁,才能进行操作,两个锁的申请必须发生交叉。这时要想打破死锁闭环,必须调整锁的申请顺序,总是以相同的顺序来申请锁,比如总是先申请 id 大的账户上的锁 ,然后再申请 id 小的账户上的锁,这样就无法形成导致死锁的那个闭环。//这样的话,即使发生了两个账户比如 id=1的和id=100的两个账户相互转账,因为不管是哪个线程先获得了id=100上的锁,另外一个线程都不会去获得id=1上的锁(因为他没有获得id=100上的锁),只能是哪个线程先获得id=100上的锁,哪个线程就先进行转账。这里除了使用id之外,如果没有类似id这样的属性可以比较,那么也可以使用对象的hashCode()的值来进行比较。
不使用显示的去锁,我们用信号量去控制
import java.util.Date;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit; public class UnLockTest {   public static String obj1 = "obj1";   public static final Semaphore a1 = new Semaphore(1);   public static String obj2 = "obj2";   public static final Semaphore a2 = new Semaphore(1);    public static void main(String[] args) {      LockAa la = new LockAa();      new Thread(la).start();      LockBb lb = new LockBb();      new Thread(lb).start();   }}class LockAa implements Runnable {   public void run() {      try {         System.out.println(new Date().toString() + " LockA 开始执行");         while (true) {            if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {               System.out.println(new Date().toString() + " LockA 锁住 obj1");               if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {                  System.out.println(new Date().toString() + " LockA 锁住 obj2");                  Thread.sleep(60 * 1000); // do something               }else{                  System.out.println(new Date().toString() + "LockA 锁 obj2 失败");               }            }else{               System.out.println(new Date().toString() + "LockA 锁 obj1 失败");            }            UnLockTest.a1.release(); // 释放            UnLockTest.a2.release();            Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的         }      } catch (Exception e) {         e.printStackTrace();      }   }}class LockBb implements Runnable {   public void run() {      try {         System.out.println(new Date().toString() + " LockB 开始执行");         while (true) {            if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {               System.out.println(new Date().toString() + " LockB 锁住 obj2");               if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {                  System.out.println(new Date().toString() + " LockB 锁住 obj1");                  Thread.sleep(60 * 1000); // do something               }else{                  System.out.println(new Date().toString() + "LockB 锁 obj1 失败");               }            }else{               System.out.println(new Date().toString() + "LockB 锁 obj2 失败");            }            UnLockTest.a1.release(); // 释放            UnLockTest.a2.release();            Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁         }      } catch (Exception e) {         e.printStackTrace();      }   }}//信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。
避免死锁方针
a:避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。b:只对有请求的进行封锁:你应当只想你要运行的资源获取封锁.如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。c:避免无限期的等待:如果两个线程正在等待对象结束,无限期的使用线程加入,如果你的线程必须要等待另一个线程的结束,若是等待进程的结束加入最好准备最长时间。

【温馨提示】

点赞+收藏文章,关注我并私信回复【面试题解析】,即可100%免费领取楼主的所有面试题资料!

标签: #死锁的类型主要有 #简述死锁产生的过程