前言:
现时各位老铁们对“死锁的类型主要有”大概比较看重,小伙伴们都想要知道一些“死锁的类型主要有”的相关知识。那么小编也在网摘上网罗了一些关于“死锁的类型主要有””的相关文章,希望各位老铁们能喜欢,各位老铁们快快来了解一下吧!【死记硬背】
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁产生的四个必要条件:互斥条件、不可剥夺条件、请求与保持条件、循环等待条件。
死锁的解决方案:调整申请锁的范围、调整申请锁的顺序、不使用显示的去锁,我们用信号量去控制、避免死锁方针。
【答案解析】
实现一段死锁的代码
这个是我【进入阿里面试的一道笔试题】,大家可以记下下面的代码。
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%免费领取楼主的所有面试题资料!
版权声明:
本站文章均来自互联网搜集,如有侵犯您的权益,请联系我们删除,谢谢。