Java程序员-你真的了解死锁吗

Java程序员-你真的了解死锁吗


💕"i need your breath"💕
作者:Mylvzi
文章主要内容:死锁的成因和必要条件
​​在这里插入图片描述
​​​​​

一.什么是死锁

死锁:就是多个线程/进程因为相互等待而使得各自持有的资源无法继续执行,这就叫做死锁。
我们以可重入锁为例,引入今天要学习的死锁问题

二.可重入锁

1.概念

可重入锁指的是:一个线程针对同一把锁连续加锁两次,而不死锁,就说这个锁具有可重入性;反之,则不具有可重入性

synchronnized(locker) {
	// 对locker对象再次加锁
	synchronized(locker){}
	}

如果此时我们的锁不具有可重入性,则这个代码就会出现死锁的情况
在这里插入图片描述

2.Java的synchronized具有可重入性

被synchronized修饰的对象具有可重入性,也就是一个线程能够针对同一个对象连续加锁两次,而不发生死锁
之所以不会发生死锁 ,是因为锁会记录是被哪一个线程持有的,在下一次有线程尝试对该所进行加锁时,先判断该线程是否是持有该锁的线程,如果是,直接加锁成功,这样就避免了死锁的出现

3.释放锁的时机

synchronnized(locker) {
	// 对locker对象再次加锁
	synchronized(locker){
	}// 2处
	// ......未执行完的代码
	}

对于上述代码,在执行到2处的时候会unlock一次,但是需要释放整个锁吗?或者说能释放整个锁吗?答案显然是不能,因为该线程实际上还在持有这个锁,该线程还没有完全执行完毕,如果在2处直接释放锁,则其他线程就可以对该对象进行修改访问,出现线程安全问题,所以需要等到整个代码执行完毕才能释放锁
也就是说对于上述嵌套加锁的代码来说,无论加锁多少次,必须等到最外围结束才能释放锁

三.死锁常见的几种情况

1.一个线程 一把锁

一个线程连续对同一把锁加锁两次,如果是不可重入的,就会发生死锁(synchronized不会发生)

2.两个线程 两把锁

现在有两个线程tA,tB,还有两把锁locker1和lcoker2,先让tA对locker1进行加锁,tB对locker2加锁,保证两个线程各自持有一把锁;然后再让tA对locker2加锁,tB对locker1加锁,由于locker1和locker2都还没有被释放,且释放的条件是两个线程都被执行完毕,而要想执行完毕就必须对新的对象进行加锁,由于这种矛盾,导致两个线程都会一直处于阻塞状态,发生死锁

// 定义两个用于加锁的对象
private static Object locker1 = new Object();
private static Object locker2 = new Object();

// 创建两个线程
Thread t1 = new Thread(() -> {
	// 线程1对locker1进行加锁
	synchronized(locker1) {
		try {
                // sleep非常关键  必须先让两个线程都拥有一把锁,再去交叉加锁  这样才会发生死锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
		}

	// 线程1对locker2进行加锁
	synchronized(locker2) {
		System.out.println("t1 加锁locker2成功!!!")
})

Thread t2 = new Thread(() -> {
	// 线程2对locker2进行加锁
	synchronized(locker2) {
		try {
                // sleep非常关键  必须先让两个线程都拥有一把锁,再去交叉加锁  这样才会发生死锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
		}

	// 线程2对locker1进行加锁
	synchronized(locker1) {
		System.out.println("t2 加锁locker1成功!!!")
})


打印结果:
在这里插入图片描述
此时就发生了死锁,两个线程因为相互等待对方持有锁的释放而都处于阻塞状态,导致各自代码无法继续运行,产生死锁!!!

3.N个线程,M把锁

N个线程,M把锁的情况可以看作情况2的一个扩展,而对于这种情况的解释有一个很好的例子–哲学家就餐问题
现在有5个哲学家围在一个餐桌旁边,但是餐桌上只有5根筷子(不是5双筷子),只有当一个哲学家拿到两根筷子的时候他才能就餐,对于每一个哲学家来说,他只干两件事:

  1. 思考人生
  2. 就餐

就餐规则如下:

  1. 哲学家什么时候思考人生,什么时候就餐是随机的
  2. 哲学家如果正在就餐,其结束就餐的时机是随机的
  3. 当哲学家思考人生的时候,不会拿取一根筷子
  4. 当哲学家就餐的时候,会拿起左右两侧的两根筷子

其写到这里大家其实也能发现,这里的哲学家就是线程,筷子就是
在这里插入图片描述
在一般情况下,每个哲学家就餐持有两个筷子的时候,如果其他哲学家也需要其中的一个筷子,则其他的哲学家就要阻塞等待,等待上一个哲学家就餐完毕,他才能使用,但是如果有那么一瞬间,每个哲学家同时都拿起了自己左侧的筷子,此时他们还无法就餐,因为他们还需要右侧的筷子,当他们去寻找右侧的筷子的时候,发现其他哲学家已经拿走了,那他就要等待持有右侧筷子的哲学家就餐完毕,但是每个哲学家因为都只有一根筷子,就都无法就餐完毕,导致每个哲学家都在循环等待,每个哲学家都处于阻塞状态,发生了死锁
在这里插入图片描述
综上,死锁其实就是一种bug,死锁的存在会导致线程的崩溃,资源的的浪费,那如何解决死锁呢?要想解决死锁,先要了解死锁形成的四个必要条件

四.死锁形成的四个必要条件

必要条件是指四个条件都满足,才会发生死锁,死锁形成的四个必要条件大致可以分为两部分

  1. 锁的基本特性
  2. 代码结构导致死锁出现

下面来看死锁形成的四个必要条件:

  1. 互斥使用(锁的基本特性):当一个线程已经拥有一个锁时,另一个线程也想要持有这个锁,就要阻塞等待
  2. 不可强占(锁的基本特性):当一个锁已经被一个线程持有时,其他线程无法强制强占该锁(总不能我正吃着饭你把我筷子抢走吧,那我吃啥!)
  3. 请求保持(代码结构):当一个线程已经持有一个锁的时候,尝试去持有另一个锁(tA已经持有locker1了,还想持有locker2)这是典型的吃着碗里的,看着锅里的
  4. 循环等待(代码结构):每一个线程都在等待其他线程结束来使自己持有锁,等待形成了依赖关系(就是上面的哲学家就餐问题)

只有当上述四个条件都满足的时候才会发生死锁,所以说要想形成死锁,其实也是一件不简单的事~
那如何避免死锁呢?对于必要条件1,2来说,这是锁的基本特性,是由synchronized决定的,你无法更改,可以更改的条件只有3,4

对于3来说,我们可以尝试避免嵌套结构的出现,但是有些业务场景之下我们必须要进行嵌套的调用又该怎么办?其实,有一个方法可以很好的解决3,4这两种场景,即规定锁的使用顺序
比如对于3来说,我们规定每个线程获取锁的顺序是固定的,只能先获取locker1,再获取locker2,不能直接获取locker2,这样就避免了一个线程在持有锁的前提下再去持有其他锁
在这里插入图片描述
对于4来说,就相当于规定了哲学家拿取筷子的方式,比如只能拿哲学家左右两侧编号较小的筷子
在这里插入图片描述

五.总结

本文主要讲述了死锁的成因,由可重入锁引入什么是死锁,再讲述了死锁形成的三种常见情况,最后又讲述了死锁形成的四个必要条件–互斥使用,不可强占,请求等待,循环等待,欢迎大家补充,交流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/265635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

❀My学习Linux小记录之UID和GID(用户ID和组ID)❀

❀My学习Linux小记录之UID和GID(用户ID和组ID)❀ 目录 ❀My学习Linux小记录之UID和GID(用户ID和组ID)❀ 用户ID(UID) 组ID(GID) 登陆 Linux 系统时,虽然输入的是自己…

Iceberg:ZOrder的实现及执行流程分析

ZOrder简介 使用Z-Order索引,可以按任意维度对数据进行排序,以获得更加高效且均衡地范围查询。它即可以作为一级索引,直接影响底层数据组织形式,甚至可以取代二索引(更加节省内存,吞吐量也理更高&#xff…

调度工具之dolphinscheduler篇

前言 随着开发程序的增多,任务调度以及任务之间的依赖关系管理就成为一个比较头疼的问题,随时少量的任务可以用linux系统自带的crontab加以定时进行,但缺点也很明细,不够直观,以及修改起来比较麻烦,容易出…

【MybatisPlus快速入门】(3)SpringBoot整合MybatisPlus 之 Lombok插件安装及MybatisPlus分页代码示例

目录 1.Lombok1.1 步骤1:添加lombok依赖 2.2 步骤2:安装Lombok的插件1.3 步骤3:模型类上添加注解2 分页功能2.1 步骤1:调用方法传入参数获取返回值2.2步骤2:设置分页拦截器2.3 步骤3:运行测试程序 之前我们已学习MyBatisPlus在代码示例与MyBatisPlus的简介,在这一节…

07-JVM调优工具详解及调优实战

文章目录 前置启动程序Jmap堆信息堆内存dump Jstack远程连接jvisualvm启动普通的jar程序JMX端口配置tomcat的JMX配置 jstack找出占用cpu最高的线程堆栈信息 JinfoJstat垃圾回收统计堆内存统计新生代垃圾回收统计新生代内存统计老年代垃圾回收统计老年代内存统计元数据空间统计J…

【MySQL】MySQL的数据类型

MySQL的数据类型 一、数据类型分类二、数值类型1、整数类型2、bit类型3、小数类型 三、字符串类型四、时间日期类型五、enum和set类型enum和set查找 数据类型的作用: 决定了存储数据时应该开辟的空间大小和数据的取值范围。决定了如何识别一个特定的二进制序列。 …

MySQL子查询、WITH AS、LAG查询统计数据实战

需求 给出一个比较常见的统计类业务需求:统计App(包括iOS和Android两大类)每日新注册用户数、以及累计注册用户数。 数据库采用MySQL,根据上面的需求,不难设计表如下: create table os_day_count(stat_d…

原型链污染[JavaScript]

一、原型链污染 此类型一般存在以nodejs编写的后端程序当中,其中Express是一个流行的Node.js Web应用程序框架 1.JavaScript 1.1 原理 引入 解释:直接先读代码来理解原型链污染 // let jack {b:2} console.log(typeof jack) // 它的类型是obejct con…

一文掌握分布式锁:Mysql/Redis/Zookeeper实现

目录 一、项目准备spring项目数据库 二、传统锁演示超卖现象使用JVM锁解决超卖解决方案JVM失效场景 使用一个SQL解决超卖使用mysql悲观锁解决超卖使用mysql乐观锁解决超卖四种锁比较Redis乐观锁集成Redis超卖现象redis乐观锁解决超卖 三、分布式锁概述四、Redis分布式锁实现方案…

React 中的 ref 和 refs:解锁更多可能性(上)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

安卓13上手势导航失效、手机卡死问题

问题描述&#xff1a;打开我们开发的app后&#xff0c;手势导航无法退回、无法回到桌面、无法切换应用。 使用设备&#xff1a;小米手机、MI14,、安卓13 未适配安卓13安卓x的情况下&#xff0c;检查自己的 AndroidManifest 文件&#xff0c;过滤器是否设置了 <category a…

信息安全等级保护的定义与意义

目录 前言 信息安全等级保护定义 广义上 狭义上 技术和管理 信息安全的基本要素 信息安全等级保护的意义 当前形式 形式严峻 国家安全 三个基本一个根本 预期目标 最终效果 实际意义 前言 信息安全等级保护是对信息和信息载体按照重要性等级分级进行保护的一种…

HTML5+CSS3+Vue小实例:彩带圣诞树

实例:彩带圣诞树 技术栈:HTML+CSS+Vue 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><…

【水文专业词汇】气象水文、水利工程等

水文专业词汇&#xff1a;气象水文、水利工程等 气象水文类水循环过程地区分类 水利工程类跨流域调水工程 参考 气象水文类 水循环过程 中文英文降水/降雨precipitation/rainfall径流runoff/streamflow产汇流runoff generation 地区分类 中文英文雨养作物区rain-fed agricu…

git入门指南:新手快速上手git(Linux环境如何使用git)

目录 前言 1. 什么是git&#xff1f; 2. git版本控制器 3. git在Linux中的使用 安装git 4. git三板斧 第一招&#xff1a;add 第二招&#xff1a;commit 第三招&#xff1a;push 5. 执行状态 6. 删除 总结 前言 Linux的基本开发工具介绍完毕&#xff0c;接下来介绍一…

游戏开发公司需要具备哪些能力?

中懿游游戏软件开发,游戏开发行业的竞争日益激烈&#xff0c;成功的游戏开发公司需要具备多方面的能力&#xff0c;从技术创新到市场推广&#xff0c;再到团队协作。以下是构建成功游戏开发公司所需的关键能力概览&#xff1a; 1. 游戏开发技术&#xff1a; 在技术方面&#…

【flink】状态清理策略(TTL)

flink的keyed state是有有效期(TTL)的&#xff0c;使用和说明在官网描述的篇幅也比较多&#xff0c;对于三种清理策略没有进行横向对比得很清晰。 全量快照清理(FULL_STATE_SCAN_SNAPSHOT)增量清理(INCREMENTAL_CLEANUP)rocksdb压缩清理(ROCKSDB_COMPACTION_FILTER) 注意&…

MySQL8.0聚合函数+over()函数

1、数据表内容为&#xff1a; CREATE TABLE chapter11 (shopname VARCHAR(255) NULL,sales VARCHAR(255) NULL,sale_date VARCHAR(255) NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;INSERT INTO chapter11 (shopname, sales, sale_date) VALUES(A…

Linux 系统调用

系统调用 在现代操作系统中&#xff0c;内核提供了用户进程与内核进行交互的一组接口。 这些接口让应用程序受限地访问硬件设备&#xff0c;提供了创建新进程并与已有进程进行通信的机制&#xff0c;也提供了申请操作系统其他资源的能力。 应用程序发出各种请求&#xff0c;而…

WEB渗透—PHP反序列化(八)

Web渗透—PHP反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩…