刚开始学习多线程的时候,由于对各种锁的操作不当,经常会不小心写代码,出现死锁。要么在灰度测试时发现,要么在codereview时提前。发现。不知道大家有没有这种死锁的经历,不过是一道高频面试题。面试官肯定希望你经历过。如果你没有体验过,那你得看一篇职业刻板印象体验过的文章。那么什么是死锁呢?为什么会出现死锁?什么是死锁?敖丙和小梅是公司的同事。今天他们参加了两个不同主题的会议。但是只有一台笔记本电脑,一台投影仪。敖丙接过笔记本,小美接过投影仪。然后开会进行到一半,我发现:不行!除了笔记本电脑,会议还需要给其他同事投影,小美也在另一个会议室找到了。只用投影仪是没有用的。这里甚至没有电脑。所以,我需要小美的投影仪,小美需要敖丙的电脑。他们都需要对方手中的资源,却又不能放弃自己拥有的。所以这两个会议都开不下去了。为此,会议进程延迟了两个小时。两边的老板都炸了:“开会前这些东西你怎么都不准备,你还要做?!”于是老板让敖丙写了一份检讨书,检讨事情的始末和问题的起因。细心的傻瓜一定发现了,为什么小梅不写呢?当然,因为小美和老板是……亲戚~上面的问题其实是一个死锁,所以想着能不能用代码来描述一下整个过程。于是我写了下面这段代码在review上:publicclassDeadLockDemo{publicstaticObjectlock1=newObject();//获取笔记本电脑publicstaticObjectlock2=newObject();//获取投影仪publicstaticvoidmain(String[]args){newAobing().start();new小美().start();}privatestaticclassAobingextendsThread{@Overridepublicvoidrun(){synchronized(lock1){System.out.println("Aobinggotthelaptop");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("Aobing被打断了!");}System.out.println("等待投影仪的奥饼");synchronized(lock2){System.out.println("Aobing得到了投影仪");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("傲冰已被打断");}}System.out.println("奥冰放映机");}System.out.println("奥冰释放笔记本电脑");}}privatestaticclassXiaomeiextendsThread{@Overridepublicvoidrun(){synchronized(lock2){System.out.println("小梅拿到投影仪");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("小梅被打断了!");}System.out.println("小梅在等笔记本电脑");synchronized(lock1){System.out.println("小梅拿到笔记本了");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("小梅被打断了!");}}System.out.println("小梅放出笔记本电脑");}系统em.out.println("小梅放开投影仪");}}}从上面的程序可以看出,傲冰和小美线程都需要获取锁才能访问各自的临界区,但又分别依赖对方的资源。两个线程同时进入等待对方资源释放的情况,但是都无法释放。这会造成死锁情况。死锁排查但这只是一个大概率的猜测。已经知道程序异常了,那怎么第一时间判断是不是死锁呢?我继续学习。他通过Java提供的一些检测手段进行了快速定位。Jps&JstackJps是Jdk自带的一个工具,可以查看正在运行的Java进程:ok,可以看到。DeadLockDemo的进程ID是1884,获取这个进程ID,然后使用jstack命令。jstack是一个强大的Java性能调查工具。主要用于实时跟踪进程中对应线程的堆栈信息。它可以打印出Jvm进程中所有线程的调用栈。因此,直接跟踪1884的进程ID即可。果然可以看到jstack检测到了死锁。而傲饼和小梅两个线程都在等待对方的锁被释放,就是阻塞状态。从这里,我确认程序陷入僵局**。立马跑到和小美喝咖啡的老陶身边:“老大,你看,这真的不是我的错,是我们公司资源不够,出现了死锁!我写!”老板郑重地点点头。“嗯,那你还是要想办法解决,一个问题不能连续做两次!”于是在深夜11点,敖丙进行了深刻的自我反省,默默地写下了这篇文章:《一问死锁的故事》。死锁的类型是可以的。看完上面的故事,我们回过头来继续讲关于死锁的知识。死锁有多少种类型?主要分为三种:一般死锁:这是最经典的死锁方法。意思是在多线程环境下,每个线程都需要多个资源来执行,但是这些资源又被不同的线程占用,这就造成了僵持状态。嵌套死锁:指的是锁的嵌套使用。我们上面故事中的死锁类型实际上是嵌套死锁。可重入死锁:指的是在多线程环境下,如果当前线程重复调用某个方法,可能会因为代码逻辑中的边界条件而导致死锁。所以后面无论是Synchronized还是Java中的Lock,在重入方面都会维护一个计数器记录当前线程重入的次数,从而进入不同的代码逻辑,以此来避免死锁。那么有的朋友就会担心了:“听了你的分析,我以后不敢随便用了,万一被骂怎么办!”。别担心,死锁不是那么容易发生的。你应该问一个问题:程序为什么会死锁,或者程序在什么情况下会死锁。要产生死锁,必须保证你的资源满足以下条件,而且缺一不可:互斥条件一个资源一次只能被一个线程访问,只要把资源分配给一个线程,其他线程不能再访问它,直到线程访问结束。RequestandHoldCondition如果线程已经拥有至少一种资源,则它们可以继续请求占有资源。如果非抢占式条件资源已经被其他线程占用,只能等待获取,不能因为需要资源就抢占。循环等待条件存在于竞争条件中,其中存在线程等待链,使得每个线程至少持有前一个线程所需的一个资源。也就是说,只有同时满足以上四个条件,线程才会因为资源分配发生冲突,可能会出现死锁。你可以打个比方,敖丙和小美是否属于以上四种情况?所以,不用担心,不容易陷入僵局。DeadlockRemoval那么当你确定程序出现了死锁时,你应该怎么做呢?当然大家不要慌,先给文章点个赞,先收藏起来,保证以后能找到。刚才我们说了死锁的情况就是要同时满足互斥、请求保持、不可剥夺、循环等待这四个条件,缺一不可。那么如果我们要解除僵局,是不是只需要破坏这四个条件中的任何一个就可以了呢?销毁请求和保持条件请求和保持意味着线程在请求??资源的同时必须一直持有资源,所以我们可以在一个线程开始运行之前,一次性申请整个运行过程中需要的所有资源。释放它直到你用完它。打破不可抢占的条件要达到这个目的,就意味着你要抢占其他线程已经拥有或者正在占有的资源,这是Synchronized无能为力的。但是我们可以使用Lock!在JDK层面,juc包(java.util.concurrent)提供的Lock可以轻松搞定。打破循环等待条件如果每个线程都依赖于前一个线程持有的资源,那么整个线程链就会像一条闭环的贪婪蛇,导致资源无法释放。因此,某个线程需要释放资源,从而打破循环。那么,我们平时的代码应该如何设计才能尽可能的避免死锁呢?尽量将程序设置为可中断的,将程序设置为可中断的,这样如果一个线程在死锁环境下收到中断请求,它就会主动释放手中的资源。Java多线程中有一个重要的方法interrupt()。该方法可以请求调用该方法的线程触发中断机制。线程可以自行决定是否释放资源。如果发生了死锁,只要让出资源就可以打破。除了给锁加一个时间限制,还可以给试图获取锁的线程加一个超时等待时间。如果线程不能在规定的时间内获取到锁,就会放弃,从而避免线程的无脑请求,同时也会释放线程已有的资源,让其他线程有机会获取锁,一个相对封闭的资源环境就可以打开。保持锁定顺序。如果多个线程都需要彼此持有的锁,那么尽量按照相同的顺序加锁,避免各个线程获取锁的顺序混乱造成的死锁。让我们回过头来看看关于死锁的故事。经过昨天加班的深刻反思,我重写了这段代码:publicclassDeadLockDemo{publicstaticObjectlock1=newObject();//获取笔记本电脑publicstaticObjectlock2=newObject();//获取投影仪publicstaticvoidmain(String[]args){newThread1().start();新的Thread2().start();}privatestaticclassThread1extendsThread{@Overridepublicvoidrun(){synchronized(lock1){System.out.println("Aobinggotthelaptop");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("Aobing被打断了!");系统输出。println("奥冰在等投影仪");synchronized(lock2){System.out.println("Aobing得到了投影仪");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("Aobing已被中断");}}System.out.println("奥冰放映机");}System.out.println("奥冰释放笔记本电脑");}}privatestaticclassThread2extendsThread{@Overridepublicvoidrun(){synchronized(lock1){System.out.println("小梅拿到笔记本了");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("小梅被打断了!");}System.out.println("小梅在等投影仪");synchronized(lock2){System.out.println("小梅拿到投影仪");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println("小梅被打断了!");}}System.out.println("小梅放出投影仪");}System.out.println("小梅放开笔记本电脑");}}}这段代码和第一段有什么区别?这次他们以相同的顺序获取锁。傲饼和小美线程都是先获取lock1,再获取lock2,这样两个线程谁先获取资源,就一次性持有资源,让下一个线程获取,直到资源释放,避免资源混乱相互竞争造成的,破坏请求和保持条件。节目也圆满结束了:于是我决定下次开会的时候和小美的开会时间分开。我会一次性拿到所有资源开始他的会议,结束后资源还给小美。拿着电脑,我兴高采烈地把这个计划告诉了老板。第二天,得益于与小梅的友好合作,两次会议都愉快地结束了,会议过程也非常顺利。老板很高兴,决定让我做会议安排委员,以后会议室不再添置新设备!我也很高兴。不仅升职加薪不再是梦,老板和小美的关系也更加融洽呢。综上所述,以上就是我、小美和老板的故事。其实生活中有很多僵局的场景,就像先有鸡还是先有蛋。这是一个典型的死锁错误。都说艺术源于生活,看来虫子也是源于生活。如果它们是等价的,难道bug不等于艺术吗?
