多线程04 死锁,线程可见性
前言
前面我们讲到了简单的线程安全问题以及简单的解决策略
其根本原因是cpu底层对线程的抢占式调度策略,随机调度
其他还有一些场景的问题如下
1.多个线程同时修改一个变量问题
2.执行的操作指令本身不是原子的
比如自增操作就分为三步,加载,自增,保存
3.内存可见性问题
4.指令重排序问题
下面两个问题将会在本文中被解决
前面我们说到了解决几个线程同时修改一个变量的问题,我们使用加锁的方式来解决
使用synchronized关键字
特殊用法:用synchronized修饰普通方法,此时同步监视器就变为了this
修饰静态方法的时候此时相当于使用类对象当做同步监视器
synchronized加的锁也可以称为互斥锁
1.synchronized的一些其他特性
先举个例子
public class ThreadDemo21 {public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(()->{synchronized (lock){synchronized (lock){System.out.println("hello");}}});t1.start();} }
这里我们直观上感觉,t1先持有了这个lock锁,此时在没有释放的情况下再进行加锁理论上应该会出现阻塞的情况,但是实际上并没有阻塞.这里的线程是会正确执行的?
为什么呢???
这是因为这里的两次加锁是同一个线程进行的,所以第二次锁实际上并没有添加,只是真正加了一次锁,第二次加锁实际上是以计数器的形式自增一次,而并没有真正的加锁,所以释放的时候也释放了一次.
有人问这有啥用呢???
其实是为了我们在写一些复杂逻辑的代码中可能会忘了这些加锁的过程,从而导致以上的阻塞的情况(称为死锁)
这个时候其实就巧妙的解决了问题,比如说如下情况
此时这种锁的机制称为"可重入锁"的机制
2.三种经典的死锁场景
1.一个线程一把锁
也就是我们刚刚讨论的场景,如果这个时候锁没有这个"可重入锁"的机制,我们就会发生死锁问题.
2.两个线程两把锁
举个例子,这里假设线程1拿到a锁,想获取b锁,同时线程2拿到b锁想获取a锁,此时两者都在等另一个线程释放另一个锁,就发生了僵持的效果
你可以想象两者发生一个交易,一个想先交钱,一个想先交货,两个人一直僵持而迟迟不能完成交易.
public class ThreadDemo23 {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (lock1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lock2){System.out.println("t1我拿到了两个锁");}}});Thread t2 = new Thread(()->{synchronized (lock2){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lock1){System.out.println("t1我拿到了两个锁");}}});t1.start();t2.start();} }
此时加上两个sleep是因为,希望在获取对应锁执行希望对应的一方获取到了对应的锁,此时执行就会发生僵持的效果
上述想解决僵持效果只需要将其中的一个线程的获取锁顺序的
3.n个线程m把锁
这里就涉及到一个哲学家进餐的问题
由Dijkstra提出并解决的哲学家就餐问题是典型的同步问题。该问题描述的是五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。
这个时候,加入五个人同时想进餐,这个时候就会发生每个人都拿到一只筷子,而不愿意放下,这就构成了一个死锁
谈解决方案之前,我们要先讨论一下构成死锁的四个必要条件
1.互斥使用,使用锁的过程是互斥的,一个线程拿到这个锁就,另一个线程想要获取就得阻塞等待
2.不可抢占 一个线程获取这个锁,只能等其他的线程主动释放
3.请求保持 持有a获取b
4.环路等待
这里1和2都不太容易破坏,只有3和4方便破坏
3可能是代码业务逻辑需求的
所以此时修改4是最合理的
此时想解决这个问题,提出几个思路
1.去掉一个哲学家
2.增加一支筷子
3.引入计数器,限制同时可以支持多少个人一起吃吃面
4.引入加锁的规则(较为常用,这里就可以控制获取筷子的顺序,此时给筷子排上编号,只能先获取编号小的筷子,此时2号获取了筷子1,以此类推,最后5获取了两个筷子,最后他结束了,其他线程/哲学家就可以吃到饭了)
5.银行家算法(太过复杂,一般不用)
3.内存可见性问题
老样子,先举个例子
public class ThreadDemo22 {private static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(()->{while(flag == 0){}System.out.println("线程结束!");});Thread t2 = new Thread(()->{System.out.println("请输入一个数字");Scanner sc = new Scanner(System.in);flag = sc.nextInt();});t1.start();t2.start();} }
此时我们想进行修改flag为任何值都发现是不成功的
这是因为,flag == 0这个操作分为两个指令
1.从内存中读取flag的值到寄存器中
2.读取完和0进行比较,然后进行一个跳转
在我们输入这个数字之前,其实这个while循环已经实现了很多次了
在这两个指令中,比较是没有多大开销的,然而从内存中加载的开销是比较大的
JVM认为这么多次这个变量始终没有修改,为了提高效率,直接把这个加载的动作直接优化掉了
其实可以理解为JVM的一个bug,此时我们可以使用sleep(n)让这个加载的频率降低,这样就不会优化了.但是治标不治本我们这里引入一个新的关键字volatile
用这个关键字来修饰flag就会让其强制读取内存,这样的结果就会更精确!!
网上还有一个说法就是将这里的内存(缓存)和寄存器的概念换成了"主存"和"工作内存"的概念,显得更严谨.
相关文章:

多线程04 死锁,线程可见性
前言 前面我们讲到了简单的线程安全问题以及简单的解决策略 其根本原因是cpu底层对线程的抢占式调度策略,随机调度 其他还有一些场景的问题如下 1.多个线程同时修改一个变量问题 2.执行的操作指令本身不是原子的 比如自增操作就分为三步,加载,自增,保存 3.内存可见性问题 4.指令…...
java中文转拼音(去除音调)
一、jar包 <dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.1</version></dependency> 二、代码 /*** 中文转换拼音*/ public class PinyinConvert {/**** param str 钱多多* r…...
[Android]常见的数据传递方式
Demo:https://github.com/Gamin-fzym/DataTransferDemo 1.Intent 发送页面 A 到页面 B 的 Intent 时,可以通过 Intent 的 putExtra() 方法将数据附加到 Intent 上。 在页面 B 中,通过 Intent 的 getXXXExtra() 方法获取传递的数据。 1).在A页面发送 …...

<蓝桥杯软件赛>零基础备赛20周--第7周--栈和二叉树
报名明年4月蓝桥杯软件赛的同学们,如果你是大一零基础,目前懵懂中,不知该怎么办,可以看看本博客系列:备赛20周合集 20周的完整安排请点击:20周计划 每周发1个博客,共20周(读者可以按…...

探究Kafka原理-7.exactly once semantics 和 性能测试
👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring源码、JUC源码、Kafka原理🔥如果感觉博主的文章还不错的话,请ὄ…...

【密码学引论】序列密码
第五章 序列密码 1、序列密码 定义: 加密过程:把明文与密钥序列进行异或运算得到密文解密过程:把密文与密钥序列进行异或运算得到明文以字/字节为单位加解密密钥:采用一个比特流发生器随机产生二进制比特流 2、序列密码和分组密…...

知识变现的未来:解析知识付费系统的核心
随着数字时代的发展,知识付费系统作为一种新兴的学习和知识分享模式,正逐渐引领着知识变现的未来。本文将深入解析知识付费系统的核心技术,揭示其在知识经济时代的重要性和潜力。 1. 知识付费系统的基本架构 知识付费系统的核心在于其灵活…...

【Linux基础】Linux常见指令总结及周边小知识
前言 Linux系统编程的学习我们将要开始了,学习它我们不得不谈谈它的版本发布是怎样的,谈它的版本发布就不得不说说unix。下面是unix发展史是我在百度百科了解的 Unix发展史 UNIX系统是一个分时系统。最早的UNIX系统于1970年问世。此前,只有…...
【Android知识笔记】性能优化专题(五)
App瘦身优化 随着业务迭代,apk体积逐渐变大。项目中积累的无用资源,未压缩的图片资源等,都为apk带来了不必要的体积增加。而APK 的大小会影响应用加载速度、使用的内存量以及消耗的电量。 瘦身优势: 最主要是转换率:下载转换率头部 App 都有 Lite 版渠道合作商要求了解 …...
Java基础之泛型
Java基础之泛型 一、泛型应用范围二、使用泛型方法三、泛型类 一、泛型应用范围 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调…...

WPF实战项目十五(客户端):RestSharp的使用
1、在WPF项目中添加Nuget包,搜索RestSharp安装 2、新建Service文件夹,新建基础通用请求类BaseRequest.cs public class BaseRequest{public Method Method { get; set; }public string Route { get; set; }public string ContenType { get; set; } &quo…...

C语言基础篇5:指针(二)
接上篇:C语言基础篇5:指针(一) 4 指针作为函数参数 4.1 指针变量作为函数的参数 指针型变量可以作为函数的参数,使用指针作为函数的参数是将函数的参数声明为一个指针,前面提到当数组作为函数的实参时,值传递数组的地址…...

「Verilog学习笔记」非整数倍数据位宽转换8to12
专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点,刷题网站用的是牛客网 要实现8bit数据至12bit数据的位宽转换,必须要用寄存器将先到达的数据进行缓存。8bit数据至12bit数据,相当于1.5个输入数据拼接成一个输出数据&#…...

Qt_一个由单例引发的崩溃
Qt_一个由单例引发的崩溃 文章目录 Qt_一个由单例引发的崩溃摘要关于 Q_GLOBAL_STATIC代码测试布局管理器源码分析Demo 验证关于布局管理器析构Qt 类声明周期探索更新代码获取父类分析Qt 单例宏源码 关键字: Qt、 Q_GLOBAL_STATIC、 单例、 UI、 崩溃 摘要 今…...

P8A004-系统加固-磁盘访问权限
【预备知识】 访问权限,根据在各种预定义的组中用户的身份标识及其成员身份来限制访问某些信息项或某些控制的机制。访问控制通常由系统管理员用来控制用户访问网络资源(如服务器、目录和文件)的访问,并且通常通过向用户和组授予…...

数智赋能 锦江汽车携手苏州金龙打造高质量盛会服务
作为一家老牌客运公司,成立于1956年的上海锦江汽车服务有限公司(以下简称锦江汽车),拥有1200多辆大巴和5000多辆轿车,是上海乃至长三角地区规模最大的专业旅游客运公司。面对客运市场的持续萎缩,锦江汽车坚…...

kolla-ansible 部署OpenStack云计算平台
目录 一、环境 二、安装及部署 三、测试 一、环境 官方文档:https://docs.openstack.org/kolla-ansible/yoga/user/quickstart.html rhel8.6 网络设置: 修改网卡名称 网络IP: 主机名: 网络时间协议 配置软件仓库 vim docke…...
wireshark 抓包提示
[TCP Previous segment not captured] 在TCP的传输阶段,同一台主机发出的数据段应该是连续的,即后一个包的Seq等于前一个包的SeqLen(三次握手和四次挥手是个例外)。如果wireshark发现后一个包的Seq号大于前一个包的SeqLen…...

Redis未授权访问-CNVD-2019-21763复现
Redis未授权访问-CNVD-2019-21763复现 利用项目: https://github.com/vulhub/redis-rogue-getshell 解压后先进入到 RedisModulesSDK目录里面的exp目录下,make编译一下才会产生exp.so文件,后面再利用这个exp.so文件进行远程代码执行 需要p…...
汇编:常用的输入与输出
1.字符输出 使用int 21h中断的02h号功能可以在屏幕输出一个字符,dl中存放要输出字符的ascii码。 如下代码将在屏幕输出一个字符“a”: mov ah,02hmov dl,aint 21h 2.字符输入 使用int 21h中断的01h号功能可以接受一个字符,al存放输…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...