当前位置: 首页 > news >正文

【JavaEE】多线程 -- 死锁问题

目录

  • 同一个线程一把锁, 加锁两次
    • 可重入锁解决
  • 两个线程两把锁, 相互获取对方的锁
  • M个线程, N把锁(第二种情况的推广)
    • 哲学家就餐问题
  • 死锁问题的概念
  • 解决死锁问题
    • 哲学家就餐问题的解决

同一个线程一把锁, 加锁两次

public class demo17 {public static void main(String[] args) {Object locker = new Object();Thread t = new Thread(()->{synchronized(locker){synchronized (locker){System.out.println("hello world");}}});t.start();}
}
  • 看这段代码, 我们对同一个线程的代码加了两把锁. 根据我们在线程安全里面介绍锁的使用和执行过程可以分析出:
  • 我们外面的第一把locker(锁)先拿到, 然后执行, 结果里面又要拿到locker,如果我们里面想拿到locker这把锁, 那么就必须要把代码执行完, 但是我们在我们代码现在的执行中, 我们又要拿到locker这把锁. 这就矛盾了.

在这里插入图片描述

  • 但是我们看运行结果却是正常的, 怎么回事呢?

可重入锁解决

  • 如果第一次加锁成功后, 第二次加锁的时候会判断第二次加锁的线程和第一次加锁的线程是否是同一个线程. 如果是同一个, 那么直接跳过第二次加锁的操作. 也就是没有真正加锁. 如果不是同一个线程, 才真正加锁
    在这里插入图片描述

两个线程两把锁, 相互获取对方的锁

public class demo18 {private static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {throw new RuntimeException(e);}}public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("t1 获取到 locker1");sleep(1000);synchronized (locker2) {System.out.println("t1 获取到 locker2");}}});Thread t2 = new Thread(() -> {synchronized (locker2) {System.out.println("t2 获取到 locker2");sleep(1000);synchronized (locker1) {System.out.println("t2 获取到 locker1");}}});t1.start();t2.start();}
}
  • 这段代码在执行t1线程时, t1抢占了locker1这把锁, 这个时候t1又要获取locker2这把锁. 但是在执行t2线程时, t2抢占了locker2这把锁, 这个时候t2又要获取locker1这把锁.
  • 这个时候t1占着locker1, 想获取locker2这把锁. 但是locker2这把锁被t2占着. 必须要等t2线程执行完后才能解锁.
  • 但是如果t2线程要执行完, 就要获得locker1这把锁, 可是locker1这把锁被t1线程占着. 就必须要等待t1线程执行完后解锁.
  • 结果t1在等t2执行完, t2也在等待t1线程执行完. 他们两个循环等待.

M个线程, N把锁(第二种情况的推广)

哲学家就餐问题

在这里插入图片描述

  • 那么5个哲学家(线程), 同时那个五个筷子(锁). 这个时候他们都差一根筷子才能吃意大利面. 他们都在循环等待旁边的哲学家放下筷子, 自己才能吃到意大利面
  • 可是这5个哲学家都是老顽固, 只要吃不到面条, 他们就不会放下手中的筷子. 所以他们都在等别人放下筷子.
  • 他们都不让步, 这里就导致没有一个哲学家(线程), 能吃到面条. 就形成了死锁

死锁问题的概念

Java多线程中的死锁问题是指两个或多个线程互相持有对方所需的资源而无法继续执行的情况。这种情况下,线程无法释放已经占有的资源,也无法获取自己所需的资源,导致程序无法继续执行下去。

  • 通常,发生死锁问题需要满足以下四个条件:
  1. 锁是互斥的, 也就是一个资源同时只能被一个线程占用(这个是synchronized的特点, 是无法改变的)
  2. 锁不可被抢占, 也就是A线程如果抢占了locker这把锁, B线程不能直接从A线程哪里抢过来, 必须等待A线程把这把锁使用完. (这一点对于synchronized是无法改变的)
  3. 请求和保持, 也就是A线程在保持拥有locker1这把锁的状态下, 还请求获取locker2这把锁. (吃着碗里的,想着锅里的)
  4. 循环等待(也就是我们上面的代码例子中, 每个线程等在循环等待其他线程释放自己所需要的锁)

解决死锁问题

前面两个条件是synchronized特性我们不能改变, 只能尝试改变请求和保持, 循环等待这两个条件. 这2个条件只要打破一个, 我们就可以破除死锁.

  • 避免使用多个锁:尽量减少使用多个锁,如果必须使用多个锁,确保获取锁的顺序是一致的,以减少死锁的可能性。
  • 加锁顺序:多个线程获取锁的顺序要保持一致,避免出现循环等待条件。
  • 加锁时限:在获取锁的时候设置超时时间,如果一段时间内没有获取到锁,就放弃当前的操作,释放已经持有的锁,避免长时间等待导致死锁。
  • 锁检测:通过监控线程的状态和资源的使用情况,及时检测并解决潜在的死锁问题。

哲学家就餐问题的解决

  • 我们这里采用约定加锁的顺序: 任意一个线程多把锁的时候, 要按照编号大小顺序来加锁.(拿筷子)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 对应到我们的代码实现就是这样的, 让多个线程按照编号小的顺序拿锁
public class demo18 {private static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {throw new RuntimeException(e);}}public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("t1 获取到 locker1");sleep(1000);synchronized (locker2) {System.out.println("t1 获取到 locker2");}}});Thread t2 = new Thread(() -> {synchronized (locker1) {System.out.println("t2 获取到 locker2");sleep(1000);synchronized (locker2
http://www.xdnf.cn/news/1304461.html

相关文章:

  • Unity输入系统:旧版Input_System
  • 链路聚合与软件网桥配置
  • Mac(一)常用的快捷键整理
  • JavaScript(JS)DOM(四)
  • 【数据分享】2022 年黑龙江省小麦、玉米和水稻幼苗影像数据集
  • Python基础(Flask①)
  • 基于机器学习的赌博网站识别系统设计与实现
  • 数据结构——顺序表单链表oj详解
  • 8.15 机器学习(2)K最近邻算法
  • k8s注意事项
  • Nginx反向代理Tomcat实战指南
  • 8月4日实训考察:重庆五一职院走进成都国际影像产业园
  • PCA降维 提升模型训练效率
  • 【科研绘图系列】R语言绘制多种饼图
  • nVidia Tesla P40使用anaconda本地重编译pytorch3d成功加载ComfyUI-3D-Pack
  • 前端动画库之gsap
  • 深入解析五大通信协议:TCP、UDP、HTTP_HTTPS、WebSocket与GRPC
  • Al大模型-本地私有化部署大模型-大模型微调
  • 腾讯位置商业授权微信小程序逆地址解析(坐标位置描述)
  • day29-进程和线程(2)
  • C语言:指针(5)
  • lcm通信库介绍与使用指南
  • 使用Docker容器化Python测试Pytest项目并配置GitHub Actions CI/CD流程
  • Pytest项目_day16(yaml和parametrize结合)
  • week1-[循环嵌套]蛇
  • Vue2与Vue3生命周期函数全面解析:从入门到精通
  • Linux操作系统--多线程(锁、线程同步)
  • 基本电子元件:贴片电阻器的种类
  • 达梦数据库使用控制台disql执行脚本
  • Mac(二)Homebrew 的安装和使用