文章目录

  • 1. 死锁
  • 2. 定位死锁
    • 2.1 jstack工具使用
    • 2.2 jconsole工具使用:
  • 3. 解决死锁
    • 3.1 哲学家就餐问题
  • 4. 活锁
    • 4.1 活锁原因
    • 4.2 活锁解决
  • 5. 饥饿
  • 6. 使用ReentrantLock解决死锁、活锁、饥饿
    • 6.1 解决哲学家就餐死锁问题

1. 死锁

死锁:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

说白了就是:两个线程互相持有对方所需的资源,互不释放且互相等待

  • t1 线程获得A对象锁,接下来想获取B对象的锁
  • t2 线程获得B对象锁,接下来想获取A对象的锁

我们举个栗子:

public class DeadLock {public static void main(String[] args) {Object A = new Object(); //锁AObject B = new Object(); //锁Bnew Thread(()->{synchronized (A){log.debug("t1 lock A");sleep(0.5);synchronized (B){log.debug("t1 lock B");log.debug("操作...");}}},"t1").start();new Thread(()->{synchronized (B){log.debug("t2 lock B");sleep(0.5);synchronized (A){log.debug("t2 lock A");log.debug("操作...");}}},"t2").start();}static void sleep(double time){try {Thread.sleep((int)(1000 * time));} catch (InterruptedException e) {e.printStackTrace();}}
}

可以看到t1和t2各持有一把锁,当尝试获取对方线程锁的时候自己却不释放锁,这就导致死锁发生

2. 定位死锁

如果发生了死锁,那我们如何定位死锁呢?我们需要借助一些工具:

  • 基于命令行: jps 定位进程 id,再用 jstack 定位死锁

  • 基于图形化界面:可以使用 jconsole工具

2.1 jstack工具使用

我们首先在项目路径下使用JPS命令查看正在运行的Java线程

接着我们可以使用jstack+可能发生死锁的线程ID打印线程情况:

然后我们就可以看到一些线程的信息

当然我们也能在最后面看到发生死锁的信息

2.2 jconsole工具使用:

Jconsole (Java Monitoring and Management Console),是一种基于JMX的可视化监视、管理工具、

Jconsole 基本使用:

  • 点击JDK/bin 目录下面的jconsole.exe 即可启动
  • 然后会自动自动搜索本机运行的所有虚拟机进程。
  • 选择其中一个进程可开始进行监视

可以看到本机所有的虚拟机进程

检测死锁

可以查看到线程发生死锁的信息和发生死锁的行数

注意:

  • 避免死锁要注意加锁顺序
  • 另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查

3. 解决死锁

3.1 哲学家就餐问题

在解决死锁之前,我们先来通过一个经典的问题来分析一下死锁

有五位哲学家,围坐在圆桌旁。

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
  • 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
  • 如果筷子被身边的人拿着,自己就得等待

五位哲学家相当于五个线程,无根筷子相当于五份互斥的资源,当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁(典型的持有一个资源,等待对方释放资源的场景)

这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况

下面通过具体的代码演示下这个死锁的问题:

筷子类:

public class Chopstick {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}

哲学家类:

public class Philosopher extends Thread {Chopstick left;Chopstick right;public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}private void eat() {log.debug("eating...");Sleeper.sleep(1);}@Overridepublic void run() {while (true) {// 获得左手筷子synchronized (left) {// 获得右手筷子synchronized (right) {// 吃饭eat();}// 放下右手筷子}// 放下左手筷子}}
}

测试类:

public class TestPhilosopher {public static void main(String[] args) {Chopstick c1 = new Chopstick("1");Chopstick c2 = new Chopstick("2");Chopstick c3 = new Chopstick("3");Chopstick c4 = new Chopstick("4");Chopstick c5 = new Chopstick("5");new Philosopher("苏格拉底",c1, c2).start();new Philosopher("柏拉图", c2, c3).start();new Philosopher("亚里士多德", c3, c4).start();new Philosopher("赫拉克利特", c4, c5).start();new Philosopher("阿基米德", c5, c1).start();}}

我们的程序运行一会后发生了死锁:

使用Jconsole可以看到:

-------------------------------------------------------------------------
名称: 阿基米德
状态: com.fx.Synchronized.philosopher@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
com.fx.Synchronized.philosopher.run(TestDinner.java:48)- 已锁定 com.fx.Synchronized.philosopher@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: com.fx.Synchronized.philosopher@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
com.fx.Synchronized.philosopher.run(TestDinner.java:48)- 已锁定 com.fx.Synchronized.philosopher@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: com.fx.Synchronized.philosopher@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
com.fx.Synchronized.philosopher.run(TestDinner.java:48)- 已锁定 com.fx.Synchronized.philosopher@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: com.fx.Synchronized.philosopher@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
com.fx.Synchronized.philosopher.run(TestDinner.java:48)- 已锁定 com.fx.Synchronized.philosopher@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: com.fx.Synchronized.philosopher@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
com.fx.Synchronized.philosopher.run(TestDinner.java:48)- 已锁定 com.fx.Synchronized.philosopher@7f31245a (筷子4)

这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有 活锁饥饿者 两种情况

解决方案在第六节

4. 活锁

4.1 活锁原因

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束

举个栗子:

public class LiveLock {static volatile int count = 10;static final Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 期望减到 0 退出循环while (count > 0) {Sleeper.sleep(0.2);count--;log.debug("count: {}", count);}}, "t1").start();new Thread(() -> {// 期望超过 20 退出循环while (count < 20) {Sleeper.sleep(0.2);count++;log.debug("count: {}", count);}}, "t2").start();}
}

观察程序运行结果,我们会发现这两个线程在一直运行,且不会停止,因为自己线程的停止条件被其他线程修改了,导致线程停止不了

活锁和死锁不同,死锁是线程发生堵塞,而活锁是线程不发生堵塞但是却不会停止

4.2 活锁解决

  • 让互相影响的线程执行的顺序交错开
  • 睡眠的时间差异化
  • 在开发中可以通过增加随机睡眠时间来避免活锁

5. 饥饿

饥饿:线程因无法访问所需资源而无法执行下去的情况,说白了就是:假设有1万个线程,还没等前面的线程执行完,后面的线程就饿死了

很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束

先来看看使用顺序加锁的方式解决之前的死锁问题,就是两个线程对两个不同的对象加锁的时候都使用相同的顺序进行加锁

我们看下面的图,当线程1和线程2都分别持有一把锁(获取锁的顺序不一样),又想要获取对方锁的时候就会发生死锁情况

我们可以通过顺序加锁的方式来避免这种死锁,即线程1和线程2都按A、B的顺序去获取锁

显然这样就可以解决死锁的情况发生,但是这样又可能会导致饥饿的现象发生

这里以上面哲学家就餐的例子演示一下:

我们看之前的代码,都是按顺序获取锁

我们现在将其改成不按顺序争抢锁

我们观察运行结果就会发现一个神奇的现象

  • 首先并没有发生死锁
  • 赫拉克利特线程获取锁的频率最高,等运行几次后阿基米德线程就再也没有获取到锁了

这样就是一种线程饥饿的现象发生了,即一些线程一直等不到cpu调度

6. 使用ReentrantLock解决死锁、活锁、饥饿

ReentrantLock相对于 synchronized 它具备如下特点

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁
  4. 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待

ReentrantLock基本使用思维导图

6.1 解决哲学家就餐死锁问题

我们知道导致死锁必须的四个条件:

  1. 互斥条件:共享资源被一个线程占用
  2. 请求与保持条件(占有且等待):一个进程因请求资源而被阻塞时,对已经获得资源保持不释放
  3. 不可剥夺条件(不可抢占):进程已获得资源,在未使用完之前,不能进行剥夺
  4. 循环等待条件:多个线程 循环等待资源,而且是循环的互相等待

对应着如果想要避免死锁,我们只需要让上面的四个条件其中一个不成立即可

  1. 请求与保持条件:放大锁范围,去除对资源的抢占
  2. 不剥夺:换成可重入锁ReentrantLock
  3. 循环等待:改成顺序加锁,避免循环等待
  4. 互斥是多线程的特性,所以这个条件无法避免

我们可以看到发生死锁的代码主要是下面的代码

当前线程获得左手筷子线程时,由于右手筷子拿不到就会一直等待,导致死锁(锁不可剥夺)

// 获得左手筷子
synchronized (left) {// 获得右手筷子synchronized (right) {// 吃饭eat();}// 放下右手筷子
}
// 放下左手筷子

现在我们可以通过ReentrantLock的tryLock对上面的代码进行改进

首先让筷子类继承自ReentrantLock,让其拥有锁的性质

public class ReentrantLockChopstick extends ReentrantLock {...}

接着我们改写上面的代码

while (true) {// 获得左手筷子if(left.tryLock()){try {//尝试获得右手筷子if(right.tryLock()){try {eat();} finally {right.unlock();}}} finally {left.unlock();}}// 放下左手筷子
}

接着测试一下,我们会发现死锁问题解决

总之,死锁的问题就是要解决资源不可剥夺、循环等待、请求与保持条件等问题,而在Java并发包内提供了很多工具类和方法来为我们保证多线程下程序的安全性

死锁、活锁、饥饿定位死锁解决死锁相关推荐

  1. java避免活锁.死锁的解决,死锁 活锁 饥饿 出现原因及解决方案

    文章目录 死锁 概念 死锁示例 为什么会出现死锁呢? 如何解决死锁呢? 解决死锁代码实现 活锁 概念 活锁示例: 如何解决活锁呢? 饥饿 概念 如何解决饥饿呢? 死锁 概念 死锁:一组互相竞争资源的线 ...

  2. 什么叫死锁?死锁案例?死锁必须满足哪些条件?如何定位死锁问题?有哪些解决死锁策略?哲学家问题?

    1.死锁是什么? 死锁一定发生在并发环境中,死锁是一种状态,当两个(或者多个线程)相互持有对方所需要的资源,却又都不主动释放手中持有的资源,导致大家都获取不到自己想要的资源,所有相关的线程无法继续执行 ...

  3. 递归锁,死锁,使用递归锁解决死锁,信号量

    递归锁 互斥锁 from threading import Lock lock_1 = Lock() lock_1.acquire() lock_1.acquire() print(123) # 打印 ...

  4. Java多线程之死锁编码及定位分析

    Java多线程之死锁编码及定位分析 目录 死锁是什么 代码实现 死锁解决办法 1. 死锁是什么 死锁是指两个或两个以上的进程在执行过程中因争夺资而造成的一种互相等待的现象,若无外力干涉那它们都将无法推 ...

  5. java 活锁 线程饿死,JAVA并发编程(四)线程死锁、饥饿、活锁

    JAVA并发编程(四)线程死锁 线程死锁 什么是线程死锁呢? 为什么会线程死锁呢? 如何避免线程死锁? 什么是饥饿呢? 什么是活锁呢? 线程死锁 什么是线程死锁呢? 死锁是指两个或两个以上的线程在执行 ...

  6. 死锁与活锁的原因 与解决方法(附加“饿死”)

    死锁与活锁的原因 与解决方法(附加"饿死") 参考文章: (1)死锁与活锁的原因 与解决方法(附加"饿死") (2)https://www.cnblogs.co ...

  7. 死锁与活锁、死锁与饥饿区别

    1.什么是死锁 死锁,是指两个或两个以上的进程(或线程)执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用干预,他们都死等下去,也都将无法往下推进. 产生死锁的必要条件: 1.互斥条件:所 ...

  8. 线程死锁的成因?如何查找并定位死锁,解决死锁?这里教你几招~

    目录 什么是死锁? 死锁代码案例: 出现死锁的原因 教你如何定位死锁位置~ 死锁解决方法 什么是死锁? 死锁就是指线程t1要使用的资源被线程t2占用,线程t2想使用的资源被线程 t1占用,这就像两股绳 ...

  9. java 死锁和饥饿_死锁与活锁,死锁与饥饿的区别

    一.定义: 1.死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等 ...

最新文章

  1. 如何选择数据分析可视化工具?Excel, Tableau还是Power BI?
  2. 《Cisco ASA设备使用指南》一2.8 Cisco ASA吉比特以太网模块
  3. C#入门面向对象编程(委托的使用)
  4. Linux用户和组相关的配置文件
  5. Leetcode题解(26)
  6. DataTabel中关于ImpotRow的一点尝试
  7. webservice 心得
  8. Ontology与OO作为一种需求分析或软件构建方法的存在意义
  9. Altium Designer -- 精心总结
  10. 电商如何抢占“双十一”?试试自动化仓库机器人 | 行业
  11. 海豚调度的安装和使用
  12. xmind文件不见了处理方法
  13. 谷歌上做SEO价钱大概多少,Google优化怎么收费?
  14. poj 1837 Balance
  15. N1 从 armbian 刷回 webpad 方法
  16. grafana 使用MySQL存储数据
  17. 将本地图片上传至七牛云
  18. 如何解决Collectors#toMap报Duplicate key xxx错误问题
  19. IDEA Maven 依赖分析插件Maven Helper
  20. 怎么把歌曲剪切合并在一起?

热门文章

  1. 解决联想710s更换nvme协议固态后重装系统出现蓝屏代码inaccessible_boot_device无限重启问题
  2. 妙趣横生的算法-常胜将军
  3. tg3269c网卡驱动linux,TG-3269C自适应PCI网卡驱动
  4. robotframework-ride生成新建机器人标志的快捷方式
  5. 网上为什么这么多人质疑和抹黑小罐茶暖莘茶呢?是因为不好吗?
  6. 廊坊金彩教育:网店定价要遵循哪些原则
  7. 职业规划测试皮肤软件,LOL2020年8月5日体验服更新内容介绍 10.18灵能特工系列皮肤上线...
  8. CT 系统参数标定及反投影重建成像-2017数模国赛论文A298编程分析
  9. 《软件测试技术实战:设计、工具及管理》—第2章 2.1节运用等价类/边界值设计测试用例...
  10. 问题 D: 生理周期