线程学习(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.rhkb.cn/news/225502.html

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

相关文章

漏洞复现--用友NC Cloud XXE

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

【译文】IEEE白皮书 6G 太赫兹技术的基本原理 2023版

第一章 简介 太赫兹波是介于微波和光波之间的光谱区域&#xff0c;频率从 0.1THz ~ 10THz 之间&#xff0c;波长在 3mm ~ 30μm 之间。提供大块连续的频带范围以满足对 Tbit/s 内极高数据传输速率的需求&#xff0c;使该区域成为下一代无线通信&#xff08;6G&#xff09;的重…

Django(三)

1.快速上手 确保app已注册 【settings.py】 编写URL和视图函数对应关系 【urls.py】 编写视图函数 【views.py】 启动django项目 命令行启动python manage.py runserverPycharm启动 1.1 再写一个页面 2. templates模板 2.1 静态文件 2.1.1 static目录 2.1.2 引用静态…

《软件需求分析报告》

第1章 序言 第2章 引言 2.1 项目概述 2.2 编写目的 2.3 文档约定 2.4 预期读者及阅读建议 第3章 技术要求 3.1 软件开发要求 第4章 项目建设内容 第5章 系统安全需求 5.1 物理设计安全 5.2 系统安全设计 5.3 网络安全设计 5.4 应用安全设计 5.5 对用户安全管理 …

html旋转相册

一、实验题目 做一个旋转的3d相册&#xff0c;当鼠标停留在相册时&#xff0c;相册向四面散开 二、实验代码 <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" con…

Tg-5511cb: tcxo高稳定性+105℃高温

爱普生推的一款TG-5511CB是一种高稳定的TCXO温补晶体振荡器&#xff0c;频率范围十分广泛从 10mhz ~ 54mhz&#xff0c;它的电源电压只需要3.3V&#xff0c;无论是手机还是其他电子设备&#xff0c;都能轻松提供稳定的电力支持。频率/温度特性表现出色&#xff0c;0.28 10^6Ma…

uniapp中uview的text组件

基本使用&#xff1a; 通过text参数设置文本内容。推荐您使用:textvalue的形式 <u--text text"我用十年青春,赴你最后之约"></u--text>设置主题&#xff1a; 通过type参数设置文本主题&#xff0c;我们提供了五类属性。primary error success warning…

ARCGIS PRO SDK GeometryEngine处理独立几何图形的函数

1、面积类&#xff1a;pol为Polygon 1).Area&#xff1a;获取几何图形的面积。这是使用二维笛卡尔数学来计算面积的平面测量 double d GeometryEngine.Instance.Area(pol) 2).GeodesicArea:获取几何图形的椭球面积 …

k8s是什么

生么是k8s&#xff1a; Kubernetes:8个字母省略&#xff0c;就是k8s 自动部署&#xff0c;自动扩展和管理容器化部署的应用程序的一个开源系统、 k8s是负责自动化运维管理多个容器化程序的集群&#xff0c;是一个功能强大的容器编排工具。 分布式和集群化的分布式进行容器管…

nodejs+vue+ElementUi大学新生入学系统的设计与实现1hme0

采用B/S模式架构系统&#xff0c;开发简单&#xff0c;只需要连接网络即可登录本系统&#xff0c;不需要安装任何客户端。开发工具采用VSCode&#xff0c;前端采用VueElementUI&#xff0c;后端采用Node.js&#xff0c;数据库采用MySQL。 涉及的技术栈 1&#xff09; 前台页面…

简析SoBit 跨链桥图文教程

从BTC网络到Solana网络桥接BRC20 1.打开SoBit平台&#xff1a;在您的网络浏览器中启动SoBit Bridge应用程序。 2.连接您的钱包&#xff1a; 选择SoBit界面右上角的比特币网络来连接您的数字钱包。 3.选择源链、目标链和您想桥接的代币&#xff1a; 从下拉菜单中选择’BTC’作为…

项目应用多级缓存示例

前不久做的一个项目&#xff0c;需要在前端实时展示硬件设备的数据。设备很多&#xff0c;并且每个设备的数据也很多&#xff0c;总之就是数据很多。同时&#xff0c;设备的刷新频率很快&#xff0c;需要每2秒读取一遍数据。 问题来了&#xff0c;我们如何读取数据&#xff0c…

支付宝、学习强国小程序input、textarea数据双向绑定

前言 和 vue 的绑定有些区别&#xff0c;需要注意。直接 value"{{inputValue}}" 是无法双向绑定的。 正确思路 文档说的比较详细&#xff0c;不过没有组合使用的案例&#xff0c;需要自行理解。这里正确的方法是先用 value 绑定数据&#xff0c;再使用 onInput 事件…

关于Sneaky DogeRAT特洛伊木马病毒网络攻击的动态情报

一、基本内容 作为复杂恶意软件活动的一部分&#xff0c;一种名为DogeRAT的新开源远程访问特洛伊木马&#xff08;RAT&#xff09;主要针对位于印度的安卓用户发动了网络安全攻击。该恶意软件通过分享Opera Mini、OpenAI ChatGOT以及YouTube、Netfilx和Instagram的高级版本等合…

7.7、kali linux环境下搭建DVWA

目录 一、资料下载准备工作 1.1、DVWA源代码下载 二、开启Apache、mysql服务 2.1、下载Apache2文件 2.2、开启Apache2服务 方法一&#xff1a;开启Apache2服务&#xff08;手动&#xff09; 方法二&#xff1a;开启Apache2服务&#xff08;系统自启动&#xff09; 2.3、…

面试题之二HTTP和RPC的区别?

面试题之二 HTTP和RPC的区别&#xff1f; Ask范围&#xff1a;分布式和微服务 难度指数&#xff1a;4星 考察频率&#xff1a;70-80% 开发年限&#xff1a;3年左右 从三个方面来回答该问题&#xff1a; 一.功能特性 1)HTTP是属于应用层的协议&#xff1a;超文本传输协议…

TypeScript下载安装,编译运行

TypeScript是拥有类型的JavaScript超集&#xff0c;它可以编译成普通、干净、完整的JavaScript代码。 简单理解&#xff1a;TypeScript就是加强版的JavaScript。 TypeScript最终会被编译成JavaScript代码&#xff0c;那么我们必然需要对应的编译环境 环境搭建前提&#xff1a…

Upload上传图片,回显图片,编辑图片,限制图片,不显示上传图标,图片放大功能

效果图&#xff1a; 新增、编辑时&#xff1a;限制上传四张&#xff0c;当超过四张隐藏上传图标 图片放大 &#xff1a;效果图 详情&#xff1a;回显时不显示上传图标 页面&#xff1a;template 部分 图片在前端存储&#xff0c;提交时一并给后端 :file-list"repairPlan…

python 通过opencv及face_recognition识别人脸

效果&#xff1a; 使用Python的cv2库和face_recognition库来进行人脸检测和比对的 0是代表一样 认为是同一人。 代码&#xff1a; pip install opencv-python pip install face_recognition# 导入cv2库&#xff0c;用于图像处理 import cv2 # 导入face_recognition库&#…

Java框架基础--maven,http,postman

maven Maven 提供了一个标准的构建生命周期和一组约定的目录结构&#xff0c;以简化和规范项目的构建过程。它主要用于 Java 项目&#xff0c;但也可以用于其他类型的项目。提高了项目的可维护性、可重复性和一致性&#xff0c;简化了构建和依赖管理的复杂性&#xff0c;使得开…