声明:尊重他人劳动成果,转载请附带原文链接!学习交流,仅供参考!

文章目录

  • 1、死锁的概念以及危害
    • 1.1 发生场景
    • 1.2 概念
    • 1.3 死锁的危害
    • 1.4 死锁的四个必要条件
  • 2、必然死锁的例子
  • 3、死锁排除以及分析方法
    • 3.1 jstack方法
    • 3.2 ThreadMXBean
  • 4、死锁修复策略
    • 4.1 避免死锁
    • 4.2 检测与恢复策略
    • 4.3 鸵鸟策略
  • 5、实际项目中如何避免死锁?
  • 6、什么是活锁?
    • 6.1 概念
    • 6.2 解决活锁的方法
  • 7、什么是饥饿?
    • 7.1 概念
    • 7.2 造成饥饿的原因

1、死锁的概念以及危害

1.1 发生场景

发生在并发

多线程/多进程改善了系统资源的利用率并且还提高了系统的处理能力,但是并发也带了新的问题----->死锁

1.2 概念

死锁是指两个或者两个以上的线程在执行过程中,由于竞争系统资源而造成的一种阻塞的现象,若没有外力的作用下,它们都将无法执行下去,此时系统就处于死锁或者说系统产生了死锁。

1.3 死锁的危害

死锁的影响在不同系统中是不一样的,取决于系统对死锁 的处理能力。

  • JVM:无法自动处理
  • 数据库中:检测并放弃事务(剥夺)

1.4 死锁的四个必要条件

  • 互斥条件

互斥条件是指,在同一时间段内,某个资源只能被一个线程占用,如果此时还有其他线程想请求该资源,那么就只能等待当前线程执行完成释放后,才能使用。

  • 请求与保持条件

请求与保持条件指当前已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

  • 不剥夺条件

不剥夺条件指线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

  • 循环等待条件

循环等待条件指在发生死锁时,必然存在一个线程——资源的环形链,即线程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

2、必然死锁的例子

当两个线程或者两个线程以上之间都相互持有对方所需要的系统资源,但是都不主动释放,一直互相等待,这样就导致线程之间都无法继续前行,导致程序陷入无限的阻塞。

代码演示

不同的flag取值 执行不同的线程

/*** @author delingw* @version 1.0*/
public class DeadThread implements Runnable {public int flag = 1;private static Object lock1 = new Object();private static Object lock2 = new Object();@Overridepublic void run() {if (flag == 1) {synchronized (lock1) {System.out.println("flag==1");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println("成功的拿到了lock2");}}}if (flag == 0) {synchronized (lock2) {System.out.println("flag==0");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1) {System.out.println("成功拿到了lock1");}}}}public static void main(String[] args) throws InterruptedException {DeadThread thread1 = new DeadThread();DeadThread thread2 = new DeadThread();thread1.flag = 1;thread2.flag = 0;Thread t1 = new Thread(thread1);Thread t2 = new Thread(thread2);t1.start();t2.start();}
}

运行结果

程序一直处死锁

而多个线程之间就是形成链路,相互依赖的状态。

3、死锁排除以及分析方法

3.1 jstack方法

利用快捷键 windows+R打开cmd窗口,输入jps,就可以看到我们执行的这个java程序的进程号(pid),然后在通过jstack pid(当前Java程序的进程号),就可以查看当前进程的错误信息。


向下拉就可以看到找到一处死锁

3.2 ThreadMXBean

代码展示

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class DeadThread implements Runnable {public int flag = 1;private static Object lock1 = new Object();private static Object lock2 = new Object();@Overridepublic void run() {if (flag == 1) {synchronized (lock1) {System.out.println("flag==1");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println("成功的拿到了lock2");}}}if (flag == 0) {synchronized (lock2) {System.out.println("flag==0");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1) {System.out.println("成功拿到了lock1");}}}}public static void main(String[] args) throws InterruptedException {DeadThread thread1 = new DeadThread();DeadThread thread2 = new DeadThread();thread1.flag = 1;thread2.flag = 0;Thread t1 = new Thread(thread1);Thread t2 = new Thread(thread2);t1.start();t2.start();Thread.sleep(1000);ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();long[] threads = threadMXBean.findDeadlockedThreads();if (threads != null && threads.length > 0) {for (int i = 0; i < threads.length; i++) {ThreadInfo threadInfo = threadMXBean.getThreadInfo(threads[i]);System.out.println("发现死锁了" + threadInfo.getThreadName());}}}
}

运行结果

直接在控制台输出。

4、死锁修复策略

4.1 避免死锁

哲学家就餐的换手方案。

  • 思路

避免相反的获取锁的顺序。比如转账时为了避免死锁,可以采用hashcode来决定获取锁的顺序,冲突时再添加其他操作。

  • 哲学家进餐问题描述

哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。
哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。

  • 流程
    1.先拿起左手的筷子
    2.然后拿起右手的筷子
    3.如果筷子被使用了,等待别人用完
    4.吃完,然后把筷子放回原位(在这里不考虑卫生问题,滑稽)

代码演示

/*** @author delingw* @version 1.0* 哲学家问题  只做两件事 1、 思考 2、吃饭*/
public class PhilosopherProblem {public static class Philosopher implements Runnable {private Object leftChopsticks;private Object rightChopsticks;public Philosopher(Object leftChopsticks, Object rightChopsticks) {this.leftChopsticks = leftChopsticks;this.rightChopsticks = rightChopsticks;}public void doaction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + action);Thread.sleep((long) (Math.random() * 10));}@Overridepublic void run() {try {while (true) {doaction(" 思考");// 拿左筷子synchronized (leftChopsticks) {doaction("   拿起了左筷子");// 拿右筷子synchronized (rightChopsticks) {doaction("   拿起了右筷子-----吃饭");doaction("   释放了右筷子-----吃完饭了");}// 释放左筷子doaction("   释放左筷子");}}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {// 哲学家数量Philosopher[] philosophers = new Philosopher[5];// 筷子数量(取决哲学家的数量,为了就是造成一个哲学家一根筷子)Object[] chopsticks = new Object[philosophers.length];// 初始化筷子for (int i = 0; i < chopsticks.length; i++) {chopsticks[i] = new Object();}// 初始化哲学家for (int i = 0; i < philosophers.length; i++) {// 左筷子Object leftChopsticks = chopsticks[i];// 右筷子Object rightChopsticks = chopsticks[(i + 1) % chopsticks.length];// 初始化哲学家philosophers[i] = new Philosopher(leftChopsticks, rightChopsticks);// 开启线程new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();}}
}

运行结果

所有哲学家都在拿到了左边的筷子,都在等待右边的筷子而陷入了死锁。

解决方法

1. 服务员检查(避免策略)
2. 改变一个哲学家拿叉子的顺序(避免策略)

 if (i == philosophers.length - 1) {philosophers[i] = new Philosopher(rightChopsticks, leftChopsticks);} else {philosophers[i] = new Philosopher(leftChopsticks, rightChopsticks);}

3. 餐票(避免策略) 例如:5个人给四张票

4. boss调节(检查与恢复策略)

4.2 检测与恢复策略

一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁。

检测算法锁的调用链路图
1. 允许发生死锁。
2. 每次调用锁都记录。
3. 定期检查锁的调用链路图中是否存在环路
4. 一旦发生死锁,就用死锁恢复机制进行恢复。
恢复方法
1. 逐个终止进程,直到死锁消除。
2. 资源抢占,把已经分发出去的锁给收回来或者让线程回退几步。

4.3 鸵鸟策略

如果我们发生死锁的几率极其低,那我们就直接忽略它,直到死锁发生的时候,我们才去解决它。

5、实际项目中如何避免死锁?

  • 设置超时时间

Lock的tryLock(long timeout, TimeUnit timeUnit),如果超时获取锁失败就进行日志打印、警告、重启等操作。

代码演示

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author delingw* @version 1.0* 避免死锁 tryLock(long time, TimeUnit unit)   超时就放弃*/
public class TryLockMethod implements Runnable {int flag = 1;// 两把锁public static Lock lock1 = new ReentrantLock();public static Lock lock2 = new ReentrantLock();@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (flag == 1) {try {if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {try {System.out.println("线程1获取了锁1");Thread.sleep(new Random().nextInt(1000));if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {try {System.out.println("线程1获取了锁2");System.out.println("线程1获取了两把锁");break;} finally {lock2.unlock();}} else {System.out.println("线程1获取锁2失败,已重试");}} finally {lock1.unlock();Thread.sleep(new Random().nextInt(1000));}} else {System.out.println("线程1获取锁1失败,已重试");}} catch (InterruptedException e) {e.printStackTrace();}}if (flag == 0) {try {if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {try {System.out.println("线程2获取了锁2");Thread.sleep(new Random().nextInt(1000) );if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {try {System.out.println("线程2获取了锁1");System.out.println("线程2获取了两把锁");break;} finally {lock1.unlock();}} else {System.out.println("线程2获取锁1失败,已重试");}} finally {lock2.unlock();Thread.sleep(new Random().nextInt(1000));}} else {System.out.println("线程2获取锁2失败,已重试");}} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {TryLockMethod method1 = new TryLockMethod();method1.flag = 1;TryLockMethod method2 = new TryLockMethod();method2.flag = 0;Thread t1 = new Thread(method1);Thread t2 = new Thread(method2);t1.start();t2.start();}
}

运行结果

  • 多用并发类而不是自己设计锁
  • 尽量降低锁的粒度:用不同的锁而不是一个锁
  • 如果能使用同步代码块,就不使用同步方法,因为同步代码块可以自己指定锁对象
  • 给你的线程起个名字,这样在debug或者在排查的时候可以减少很多工作量,框架和JDK都是遵守这个最佳实践
  • 避免锁的嵌套:例如我们上面的必然死锁
  • 分配资源前先看能不能收回来:例如操作系统中讲的银行家算法
  • 尽量不要几个功能用一把锁:可以专锁专用

6、什么是活锁?

6.1 概念

活锁是指虽然线程没有阻塞,也始终运行,但是程序得不到进展,因为线程始终重复做同样的事。一直谦让,导致资源一直在线程间跳动。

6.2 解决活锁的方法

  • 以太网的指数退避算法
  • 加入随机因素
public class LiveLock {static class Spoon {  //勺private Diner owner;public Spoon(Diner owner) {this.owner = owner;}public Diner getOwner() {return owner;}public void setOwner(Diner owner) {this.owner = owner;}public synchronized void use() {System.out.printf("%s吃完了!", owner.name);}}static class Diner {private String name;private boolean isHungry;public Diner(String name) {this.name = name;isHungry = true;}public void eatWith(Spoon spoon, Diner spouse) {while (isHungry) {if (spoon.owner != this) {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}continue;}Random random = new Random();if (spouse.isHungry && random.nextInt(10) < 9) {System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");spoon.setOwner(spouse);continue;}spoon.use();isHungry = false;System.out.println(name + ": 我吃完了");spoon.setOwner(spouse);}}}public static void main(String[] args) {Diner husband = new Diner("牛郎");Diner wife = new Diner("织女");Spoon spoon = new Spoon(husband);new Thread(new Runnable() {@Overridepublic void run() {husband.eatWith(spoon, wife);}}).start();new Thread(new Runnable() {@Overridepublic void run() {wife.eatWith(spoon, husband);}}).start();}
}

7、什么是饥饿?

7.1 概念

当线程需要某些资源比如CPU,却始终得不到。

7.2 造成饥饿的原因

线程优先级设置过低,或者有线程持有锁同时又无限循环从而不释放锁,或者某程序始终占用某文件的写锁。

什么是死锁、活锁、饥饿?相关推荐

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

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

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

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

  3. 操作系统——死锁和饥饿

    操作系统--死锁和饥饿 转自:https://blog.csdn.net/qq_42192693/article/details/103054682 1.概念 死锁:如果一组进程中的每一个进程都在等待 ...

  4. 操作系统-并发:死锁和饥饿

    知识架构 本章介绍并发处理中需要解决的两个问题:死锁和饥饿.本章首先讨论死锁的基本原理和饥饿的相关问题:接着分析处理死锁的三种常用方法:预防.检测和避免:然后考虑用于说明同步和死锁的一个经典问题:哲学 ...

  5. 【数据库技术】2PL(两阶段锁)下的死锁与饥饿处理手段

    事务处理-2PL下的死锁与饥饿 2PL--2阶段锁存在的问题 一.死锁与等待图 1. 死锁(Deadlock) 2. 等待图(Wait-for graph) 二.死锁的处理手段 1. 死锁预防 2. ...

  6. 死锁与活锁的区别,死锁与饥饿的区别?

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

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

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

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

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

  9. 操作系统:线程死锁、饥饿、活锁

    1. 死锁 可以认为是两个线程或进程在请求对方占有的资源. 出现以下四种情况会产生死锁: 情况一:相互排斥.一个线程或进程永远占有共享资源,比如,独占该资源. 情况二:循环等待.例如,进程A在等待进程 ...

  10. 死锁和饥饿-哲学家就餐问题

    哲学家就餐问题 背景: 假设5位哲学家住在一起(可以推广到n),每天生活就是思考和吃饭,每位哲学家需要2把叉子来吃意大利面. 就餐安排: 一张圆桌,5个板凳,5个盘子,5把叉子,每个想吃饭的哲学家做到 ...

最新文章

  1. Windows中的tree命令不可用的解决办法
  2. 技术 KPI 的量化
  3. hdu 5996 dingyeye loves stone(博弈)
  4. WebLogic—安装
  5. 织梦mysql安装教程视频教程_dedecms织梦模板安装教程视频/图文步骤(模板秀出品)...
  6. docker镜像下载太慢
  7. Python 算法交易实验30 退而结网7-交易策略思考
  8. JAVA 疯狂讲义 学习笔记
  9. 解析PayPal支付接口的PHP开发方式
  10. 《Using OpenRefine》翻译~10
  11. 分数加减乘除混合运算带答案_分数加减乘除混合运算专项训练
  12. Python词云库wordcloud 显示中文 !!!
  13. 【小白话通信】离散分布的生成
  14. 什么是外包公司你知道?
  15. Python:统计大小写字母个数和数字个数
  16. VUE项目制作大致方法和流程
  17. 刷题日记【第十三篇】-笔试必刷题【数根+星际密码+跳台阶扩展问题+快到碗里来】
  18. web前端面试题(面试题大全)
  19. 反垃圾邮件网关MailCleaner安装与配置2
  20. 苹果公司创始人沃兹尼艾克加入创业公司

热门文章

  1. 关于Linux你不知道的那些往事
  2. 【医学+深度论文:F04】Integrated Optic Disc and Cup Segmentation with Deep Learning
  3. 港澳台等各地手机号码格式正则校验
  4. 彗星驱动备份专家 V1.0
  5. pdf如何删除其中一页?不妨试试这些办法
  6. php 上传到爱奇艺,dedecms怎么上传视频
  7. 10.24 应用层程序控制数码管显示数字
  8. HEC-RAS建模与案例应用
  9. bigquant的策略代码
  10. unity3d搭建安卓环境