线程学习(3)-volatile关键字,wait/notify的使用

​ 💕"命由我作,福自己求"💕
作者:Mylvzi
文章主要内容:线程学习(2)​​在这里插入图片描述​​

一.volatile关键字

volatile关键字是多线程编程中一个非常重要的概念,它主要有两个功能:保证内存可见性,和禁止指令重排序

1.内存可见性

内存可见性(Memory Visibility) 指的是在多线程编程环境下,一个线程修改共享变量,其他线程能够立即看到共享变量修改后的值

先来了解一个底层知识,我们写的代码是要读取数据的,最常见的数据就是我们定义的变量,变量被保存在内存之中,系统要使用数据需要cpu进行读内存(load)的操作,而load这个操作对于cpu来说是一个非常慢的数据,因为cpu的速度是很快的,读内存这个操作比读寄存器要慢上几千倍,所以load对于cpu来说是一个很大的开销

// 设计一个标志位
private static int isQuit = 0;Thread t1 = new Thread(() -> {while(isQuit == 0) {// ......	}System.out.println("t1线程结束");
});// 在t2线程中修改isQuit的值
Thread t2 = new Thread(() -> {System.out.println("请输入isQuit的值:");Scanner scan = new Scanner(System.in);isQuit = scan.nextInt();
});t1.start();
t2.start();

结果截图:
在这里插入图片描述
在t2中将isQUit设置为1,按理说t1中的循环条件已经不满足了啊,整个进程应该会终止才对,但是什么都没有打印,这是为什么呢?这其实就和我们上面说的读内存对cpu开销大这一事实有关

先说结论,为了解决读内存的问题,并提高效率,编译器会对读取数据进行一些优化,减少读内存的次数,尽可能多的直接从cpu上读取,来提高效率

知道了这个结论,就很容易解释上述问题了:
在这里插入图片描述
编译器是好心的,但是他为了提高效率却忽视了准确性,而这个准确性对我们来说是很重要的(这涉及到整个进程的执行),不能忽视。这也属于一个编译器的bug

而关键字volatile就是用于解决这个问题,被volatile修饰的变量,编译器不会对其执行上述的优化,也就是告诉编译器,我这个变量很重要,不要为了效率就忽视准确性

当我们在t2线程中修改变量isQuit时,实际上是内存中的isQuit发生了改变,其他线程(t1)能够立即看到这个改变后的值,循环终止,这就是volatile关键字的内存可见性功能,它保证了共享变量在所有线程中的公开,透明,其他线程能够立即看到共享变量的改变,及时做出调整。
在这里插入图片描述
在输入1之后,t1线程结束循环,立即终止
内存可见性也是线程安全问题的一种,之前学习过的一个线程安全问题是两个线程同时针对同一个变量进行修改,但实际上两个线程,一个线程修改变量,一个线程读取变量可能也会发生线程安全问题

再补充一点,编译器之所以进行优化是因为短时间内大量的load操作.如果我们放慢/减少 load,编译器就不会进行优化,最常见的方式就是添加sleep

        Thread t1 = new Thread(() -> {while(isQuit == 0) {// 让线程休眠一会儿try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}// ......}System.out.println("t1线程结束");});

运行结果:
在这里插入图片描述
由于t1内部让线程短暂阻塞了一会儿,load操作执行的次数就会大大减少,此时编译器就没有进行优化的必要,所以每次读取都是读内存.

补充:
volatile的本意是不稳定的,易挥发的,使用它修饰一个变量相当于告诉编译器"喂(#`O′),我这个变量是不稳定的啊,你不要把他给我加载到工作内存(cpu寄存器)中,让他老老实实的代码主内存中就行",这样编译器就不会进行优化了

2.指令重排序

指令重排序也是编译器优化的一种,它是指在保证执行结果正确的前提下,对指令的执行顺序进行调整,达到效率提高的目的,比如去菜市场买菜,我只要最后买到我要买的菜即可,我按照什么路径去买无所谓,但是我追求最快买完
在这里插入图片描述
在单线程模式下,指令重排序不会带来问题,反而还是一个好处.但是在多线程下,由于线程的调度是随机的,就有可能带来意想不到的 结果,所以我们需要禁止指令的重排序,往往是对可能涉及到指令重排序对象进行volatile的修饰,就能让编译器不对其进行优化

关于指令重排序 的具体应用会在后面的单例模式部分讲到

二.wait/notify

1.引言

在多线程编程中,我们经常要协调线程的执行顺序来实现一些场景需求,比如之前学习过的join()方法,他可以控制线程的结束顺序,也是协调线程的一种方式

但是有些时候我们想线程不结束,也能控制他们的执行顺序.而不是只能等到一个线程结束再去执行其他线程,此时,就可以使用wait/notify来实现上述需求

2.wait方法的使用

wait和notify方法都是属于Object类的方法,需要通过实例化一个object对象来进行调用
wait方法的具体执行过程分为三步:

  1. 释放当前对象的锁
  2. 阻塞等待
  3. 等待对象使用notify方法唤醒

wait方法使用的过程中有一个最常见的错误就是调用wait方法的对象事先并没有加锁,直接wait会报错

    public static void main(String[] args) throws InterruptedException {Object locker = new Object();System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");}

运行结果:
在这里插入图片描述
这里显示**"非法监视器状态异常"**,监视器 是什么?我们之前学习过synchronized,他是用于给对象加锁的,他其实还有另一个名字,叫做 监视器锁.

更本质的说,监视器(monitor)其实是一种机制,每个对象都会关联一个监视器,用于实现同步,同步就是保证多个线程对于共享变量的使用是合理的,是没有线程安全问题的(比如使用加锁来实现同步,可以避免多个线程针对同一变量进行修改这种线程安全问题).我想,这里的同步的意思是指在多线程编程中,每个线程获取到的共享资源的状态是同步的,不会出现一个线程修改了共享变量,另一个线程却还在使用修改之前的变量.

synchronized被称为监视器锁,是因为它本质上是获取到了对象关联监视器的锁,他保证了同一时间只能有一个线程访问进入synchronized修饰的代码块/方法,保证了线程安全.

回到本文,由于创建的locker对象事先并没有加锁,与其关联的监视器的并没有上锁,没有上锁你却想先释放锁,这不是bug吗,所以会报出非法监视器状态异常,为了解决这个异常,我们需要先对locker对象进行加锁

    public static void main(String[] args) throws InterruptedException {Object locker = new Object();synchronized(locker) {System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");}}

运行结果
在这里插入图片描述
注:wait方法也可以带参数,和带参数的join方法一样,可以设置等待的时间

3.notify方法的使用

wait方法会先释放调用对象的锁,然后使调用的线程处于阻塞状态,直到对象使用notify进行唤醒,notify在使用的时候也需要先获取到与对象关联的锁,否则也会抛出非法监视器状态异常,这样做也是为了保证线程安全
示例代码:

    public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() ->{synchronized (locker) {System.out.println("wait 开始");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait 结束");}});Thread t2 = new Thread(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker) {System.out.println("使用notify 方法 进行唤醒");locker.notify();}});t1.start();t2.start();}

运行结果:
在这里插入图片描述
除了使用notify,我们还可以使用notifyAll方法来一次性唤醒当前对象所有wait的线程,当然看似是一次性,实际上还是一个一个进行唤醒的,如果一次性唤醒全部,会导致锁冲突,出现线程不安全问题

今天的学习就到这里,下期预告<<多线程设计模式讲解(1)>>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面向对象设计与分析40讲(15)简单工厂方法模式

文章目录 定义示例优缺点定义 简单工厂模式是一种创建型模式,用于根据客户端的需求创建对象实例,所谓的需求反映到编程语言里就是传入的参数。 简单工厂模式包括三个主要部分: 工厂类(Simple Factory):这是整个模式的核心。它负责根据客户端的请求来创建并返回相应的对…

Maven依赖管理项目构建工具

文章目录 Maven依赖管理项目构建工具目录一、Maven简介1. Maven介绍2. Maven主要作用理解3. Maven软件工作原理模型图&#xff08;了解&#xff09; 二、Maven安装和配置1. Maven安装2. Maven环境配置3. Maven功能配置4. IDEA配置本地Maven软件 三、基于IDEA创建Maven工程1. 梳…

Anylogic Pro 8.8.x for Mac / for Linux Crack

Digital twins – a step towards a digital enterprise AnyLogic是唯一一个支持创建模拟模型的方法的模拟建模工具&#xff1a;面向过程&#xff08;离散事件&#xff09;、系统动态和代理&#xff0c;以及它们的任何组合。AnyLogic提供的建模语言的独特性、灵活性和强大性使…

自恢复保险丝

自恢复保险丝 常用电子元器件类型 0467.500NRHF 文章目录 自恢复保险丝前言一、自恢复保险丝是什么二、0467.500NRHF总结前言 自恢复保险丝通常用于电子设备、电路板、电视机、计算机、通信设备、充电器、电源适配器等各种电路保护应用。需要注意的是,选择适当的自恢复保险…

Docker部署Nexus Maven私服并实现远程访问Nexus界面

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 1. Docker安装Nexus2. 本地访问Nexus3. Linux安装Cpolar4. 配置Nexus界面公网地址5. 远程访问 Nexus界面6. 固定N…

Failed to resolve component: router-view

出现了这个问题&#xff0c;导致本该出现的页面没有出现&#xff0c;在网上看了解决办法&#xff0c;原来是没有挂载好app 原先代码&#xff1a; app.use(router) createApp(App).mount(#app) //这是又创建了一个新的app&#xff0c;无法使用到router改进&#xff1a; app.…

【51单片机系列】DS18B20温度传感器模块

本文是关于温度传感器的相关内容。 文章目录 一、 DS18B20数字温度传感器介绍1.1、 DS18B20温度传感器的特点1.2、DA18B20内部结构1.3、 DS18B20的温度转换规则1.4、 DS18B20的ROM指令表1.6、 计算温度1.7、 读写时序 二、DS18B20使用示例 一、 DS18B20数字温度传感器介绍 DS1…

Sublime Text 4 中文汉化教程(Version: Build 4169)

Sublime Text 4汉化 1 知识小课堂1.1 sublim简介1.2 其他编辑器 2 安装过程2.1 安装Install Package Control2.2 Install Package2.3 安装工具包2.4 常用的插件2.5 安装中文包 1 知识小课堂 1.1 sublim简介 Sublime是一款代码编辑器&#xff0c;致力于为开发人员提供快速、高…

解决企业TB或者PB级大文件传输速度和安全问题

随着企业数据不断增加&#xff0c;TB或PB级大文件的传输成为企业信息共享和数据备份的重要手段之一。然而&#xff0c;这些大文件的传输速度和安全问题成为制约企业发展的瓶颈&#xff0c;也是企业需要解决的重要问题。本文将探讨如何解决这些问题&#xff0c;并从以下几个方面…

海外短剧操作配置,海外短剧小程序功能介绍,海外短剧小程序搭建开发制作

一、海外短剧小程序操作&配置 完整文档联系趣图吖&#xff1a;nn7334n 二、海外短剧小程序功能 功能: 批量导入一键改价 多语言切换:英语、泰语、印尼、西班 更新) 前端: 安卓ios h5三端 营销功能强大:积分营销分销商入驻会员模式 卡密模式 单独付费 三、海外短剧小程…

如何免费下载5天以内的卫星影像?

现在&#xff0c;我们就详细分享一下如何下载5天以内卫星影像的具体操作方法。 如何下载5天以内卫星影像 在soar.earth网站中&#xff0c;点击“Enter the Atlas”按钮&#xff0c;打开地图图库。 打开地图图库 我们可以根据自己的需要&#xff0c;在右下角切换底图&#xf…

掌握的单词个数 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 有一个字符串数组 words 和一个字符串 chars。假如可以用 chars 中的字母拼写出 words 中的某个"单词"(字符串),那么我们就认为你掌握了这个单词。 words 的字等仅由 a-z 英文小写宁母组成,例如“abc”。 char…

Pytorch深度强化学习2-1:基于价值的强化学习——DQN算法

目录 0 专栏介绍1 基于价值的强化学习2 深度Q网络与Q-learning3 DQN原理分析4 DQN训练实例 0 专栏介绍 本专栏重点介绍强化学习技术的数学原理&#xff0c;并且采用Pytorch框架对常见的强化学习算法、案例进行实现&#xff0c;帮助读者理解并快速上手开发。同时&#xff0c;辅…

SQL server 数据库练习题及答案(练习3)

一、编程题 公司部门表 department 字段名称 数据类型 约束等 字段描述 id int 主键&#xff0c;自增 部门ID name varchar(32) 非空&#xff0c;唯一 部门名称 description varchar(1024) …

HTTP content-type内容类型的常见格式

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

分布式训练通信NCCL之Ring-Allreduce详解

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

fpga 8段4位数码管verilator模拟

8段4位数码管verilator模拟 seg.v module seg(input wire clk,input wire rst_n,output wire[7:0] SEG,output wire[3:0] SEL );reg[7:0] digit[0:15] {8h3f, 8h06, 8h5b, 8h4f, 8h66, 8h6d, 8h7d,8h07,8h7f,8h6f, 8h77, 8h7c, 8h39, 8h5e, 8h79, 8h71};reg[31:0] cnt 32…

Opencv_CUDA实现推理图像前处理与后处理

Opencv_CUDA实现推理图像前处理与后处理 通过trt 或者 openvino部署深度学习算法时&#xff0c;往往会通过opencv的Mat及算法将图像转换为固定的格式作为输入openvino图像的前后处理后边将在单独的文章中写出今晚空闲搜了一些opencv_cuda的使用方法&#xff0c;在此总结一下前…

云服务器ECS运维管理

目录 实时掌握CPU、内存使用情况 实时掌握存储的使用情况 定期对云服务器数据做好备份 定期检查云服务器的安全运行情况 要想保证云服务器长期稳定的使用&#xff0c;除了依靠阿里云&#xff08;云服务提供商&#xff09;的技术支持&#xff0c;自身必要的安全维护手段也是…

W6100-EVB-Pico评估版介绍

文章目录 1 简介2 硬件资源2.1 硬件规格2.2 引脚定义2.3 工作条件 3 参考资料3.1 Datasheet3.2 原理图3.3 尺寸图&#xff08;尺寸&#xff1a;mm&#xff09;3.4 参考例程 4 硬件协议栈优势 1 简介 W6100-EVB-Pico是一款基于树莓派RP2040和全硬件TCP/IP协议栈以太网芯片W6100的…