一、类型
-
一般性死锁:这是最经典的死锁方式。指的是多个线程的执行下必须拥有多个资源,但是这些资源又分别被不同的线程占有着,即造成了一种僵持的状态。
-
嵌套性死锁:指的就是锁的互相嵌套使用。上面这种情况的死锁类型,其实就属于嵌套性死锁。
重入性死锁:指的是多线程环境下,若当前线程重复调用一个方法则可能因为代码逻辑里的边界情况从而导致死锁。
所以,Java之后无论是Synchronized还是Lock在可重入方面都会维护一个计数器来记录当前线程的重入次数,从而进入不同的代码逻辑,就是为了避免死锁的发生。
二、死锁原理
-
互斥条件
某资源一次只能一个线程访问,该资源只要分配给某个线程,其它线程就无法再访问,直到该线程访问结束。
-
请求与保持条件
线程在已经占有至少一个资源的情况下还可以继续请求占有资源。
-
不可抢占条件
资源若已被其它线程占有,那么想要获取它就只能等待,不能因为你需要该资源就将其抢占。
-
循环等待条件
在竞争环境中存在一个线程等待链,使得每个线程都占有上一个线程所需的至少一种资源。
只有以上四个条件同时满足,线程才会因为资源分配产生矛盾,死锁才有可能发生。
三、死锁解除
-
破坏请求与保持条件
请求与保持指线程请求资源的同时必须始终持有资源,所以我们可以在线程开始运行之前,一次性地申请其在整个运行过程中所需的全部资源。直至使用完再释放。
-
破坏不可抢占条件
想要达到这个目的代表着你要去抢占别的线程已经或正在持有的资源,这对于Synchronized是无能为力的。但是我们可以使用Lock呀!在JDK层面,juc包(java.util.concurrent)提供的Lock可以轻轻松松做到。
-
破坏循环等待条件
若是每个线程都依赖上一线程所持有的资源,那么整个线程链就会像闭环的贪吃蛇一样,导致资源无法被释放。因此就需要某一个线程释放资源,从而打破循环。
四、应用
-
尽量将程序设置为可中断的
interrupt()方法,这个方法可以请求调用此方法的线程触发中断机制,该线程可以自身决定是否释放资源。若是已经发生了死锁,只要它放弃资源便可打破。
-
为锁添加时限
除此之外还可以为尝试获取锁的线程添加一个超时等待时间。若线程在规定时间内获取不到锁则放弃,这样就可以避免线程无脑请求,同时也会释放该线程已有的资源,让其它线程有机会获取到锁,可以开放化一个相对封闭的资源环境。
-
保持加锁顺序
对于多个线程如果需要对方所持有的锁,那么就要尽量按照相同的顺序加锁,这样就能够避免因为各个线程获取锁的顺序混乱导致死锁。