白话:
A有B需要的资源,而B又有A需要的资源,但是A和B都不释放,两个都拿不到对方的资源.就会产生死锁.
当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
1.学术化的定义
死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。只要打破四个必要条件之一就能有效预防死锁的发生:
打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。
避免死锁常见的算法有有序资源分配法、银行家算法。
2.现象
简单顺序死锁
就是多个方法加锁的顺序不一致,比如下面两个方法,A方法加锁顺序是 valueSecond — valueFirst , B方法加锁顺序是valueFirst —valueSecond
解决办法就是让两个加锁顺序一致,比如都是 valueSecond — valueFirst 或者都是 valueFirst —valueSecond
private static Object valueFirst = new Object();//第一个锁 private static Object valueSecond = new Object();//第二个锁 /获取锁顺序不对造成了死锁/ //先拿第一个锁,再拿第二个锁 private static void fisrtToSecond() throws InterruptedException { String threadName = Thread.currentThread().getName(); /加锁顺序 valueSecond — valueFirst / synchronized (valueSecond) { System.out.println(threadName + “ get 1st”); Thread.sleep(100); synchronized (valueFirst) { System.out.println(threadName + “ get 2nd”); } } } //先拿第二个锁,再拿第一个锁 private static void SecondToFisrt() throws InterruptedException { String threadName = Thread.currentThread().getName(); /加锁顺序 valueFirst — valueSecond / synchronized (valueFirst) { System.out.println(threadName + “ get 2nd”); Thread.sleep(100); synchronized (valueSecond) { System.out.println(threadName + “ get 1st”); } } } |
---|
动态顺序死锁
顾名思义也是和获取锁的顺序有关,但是比较隐蔽,不像简单顺序死锁,往往从代码一眼就看出获取锁的顺序不对。
3.危害
1、线程不工作了,但是整个程序还是活着的
2、没有任何的异常信息可以供我们检查,想找问题都不好找.
3、一旦程序发生了发生了死锁,是没有任何的办法恢复的,只能重启程序,对生产平台的程序来说,这是个很严重的问题。
实际工作中的死锁
时间不定,不是每次必现;一旦出现没有任何异常信息,只知道这个应用的所有业务越来越慢,最后停止服务,无法确定是哪个具体业务导致的问题;测试部门也无法复现,并发量不够。
4.解决死锁
定位
要解决死锁,当然要先找到死锁,怎么找?
通过jps 查询应用的 id,再通过jstack id 查看应用的锁的持有情况
https://www.yuque.com/docs/share/8b82aea4-cd8b-4ac6-9829-861b2e5e3a11?# 《使用 jstack 排查Java项目的Linux线上环境死锁》
修正
关键是保证拿锁的顺序一致
两种解决方式
1、 内部通过顺序比较,确定拿锁的顺序;
2、 采用尝试拿锁的机制。
Java中活锁和死锁有什么区别
这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
活锁是另一种形式的活跃性问题。当多个互相协作的线程都对彼此进行相应而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。就像两个过于礼貌的人在半路上面对面地相遇:他们彼此都让出对方的路,然而又在另一条路上相遇了。因此他们就这样反复地避让下去。
怎么检测一个线程是否拥有锁
我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。你可以查看这篇文章了解更多。