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

news2024/11/18 11:45:13

​ 💕"命由我作,福自己求"💕
作者: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.coloradmin.cn/o/1338442.html

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

相关文章

如何配置TLSv1.2版本的ssl

1、tomcat配置TLSv1.2版本的ssl 如下图所示&#xff0c;打开tomcat\conf\server.xml文件&#xff0c;进行如下配置&#xff1a; 注意&#xff1a;需要将申请的tomcat版本的ssl认证文件&#xff0c;如server.jks存放到tomcat\conf\ssl_file\目录下。 <Connector port"1…

MyBatis动态SQL(常用标签)

目录 标签--if 标签--trim 标签--where 标签--set 标签--foreach 和标签--sql和include 根据需求&#xff0c;动态拼接SQL&#xff0c;下面的标签示范使用xml的方式演示。 <if>标签--if 注解&#xff1a; 1.要把全部的SQL放在script标签下 2.使用if标签 可以观…

15-网络安全框架及模型-BLP机密性模型

目录 BLP机密性模型 1 背景概述 2 模型原理 3 主要特性 4 优势和局限性 5 困难和挑战 6 应用场景 7 应用案例 BLP机密性模型 1 背景概述 BLP模型&#xff0c;全称为Bell-LaPadula模型&#xff0c;是在1973年由D.Bell和J.LaPadula在《Mathematical foundations and mod…

PLC-IoT 网关开发札记(1):存档和分发 Android App

开篇记 PLC-IoT 网关是作者开发的产品&#xff0c;根据客户需求&#xff0c;立项开发手机 App&#xff0c;为用户提供一种方便、直观、友好的设备操控方式。网关运行的是嵌入式 Linux 操作系统&#xff0c;计划通过某一种通信协议&#xff08;例如 HTTP&#xff0c;MQTT或者 T…

微信小程序预览pdf,修改pdf文件名

记录微信小程序预览pdf文件&#xff0c;修改pdf名字安卓和ios都可用。 1.安卓和苹果的效果 2.需要用到的api 1.wx.downloadFile wx.downloadFile 下载文件资源到本地。客户端直接发起一个 HTTPS GET 请求&#xff0c;返回文件的本地临时路径 (本地路径)&#xff0c;单次下载…

2024年元旦节放假通知

致尊敬的客户以及全体同仁&#xff1a; 旧岁已展千重锦&#xff0c;新年再进百尺竿。在这辞旧迎新之际&#xff0c;易天光通信提前祝您元旦快乐&#xff01;生意兴隆&#xff0c;身体健康&#xff0c;万事如意&#xff01;根据国家法定假期的规定&#xff0c;并结合公司实际情…

C# 使用Pipelines处理Socket数据包

写在前面 在上一篇中对Pipelines进行简单的了解&#xff0c;同时也留下了未解的问题&#xff0c;如何将Pipelines类库运用到Socket通讯过程中来解决粘包和分包。链接地址如下&#xff1a; 初识System.IO.Pipelines https://rjcql.blog.csdn.net/article/details/135211047 这…

嵌入式单片机的存储区域与堆和栈

一、单片机存储区域 如图所示位STM32F103ZET6的参数&#xff1a; 单片机的ROM&#xff08;内部FLASH&#xff09;&#xff1a;512KB&#xff0c;用来存放程序代码的空间。 单片机的RAM&#xff1a;64KB&#xff0c;一般都被分配为堆、栈、变量等的空间。 二、堆和栈的概念 …

深入探索Spring Boot的核心功能:快速构建原生程序响应式处理数据(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 写在前面参与规则 ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论…

【linux】Linux管道的原理与使用场景

Linux管道是Linux命令行界面中一种强大的工具&#xff0c;它允许用户将多个命令链接起来&#xff0c;使得一个命令的输出可以作为另一个命令的输入。这种机制使得我们可以创建复杂的命令链&#xff0c;并在处理数据时提供了极大的灵活性。在本文中&#xff0c;我们将详细介绍Li…

什么是焊点保护胶?它的作用是什么

焊点保护胶是一种用于电子元件焊点和连接处的保护的特殊胶水。它主要作用是提供以下几点的保护和增强功能&#xff1a; 防腐蚀保护 电子元件的焊点容易受到环境中的湿度、化学物质和其他腐蚀性因素的影响。焊点保护胶能够形成一层防护膜&#xff0c;减少腐蚀的风险&#xff0c…

苏州科技大学计算机817程序设计(java) 学习笔记

之前备考苏州科技大学计算机&#xff08;专业课&#xff1a;817程序设计&#xff08;java&#xff09;&#xff09;。 学习Java和算法相关内容&#xff0c;现将笔记及资料统一整理归纳移至这里。 部分内容不太完善&#xff0c;欢迎提议。 目录 考情分析 考卷题型 刷题攻略…

Typora使用PicGo+Gitee上传图片报错403 Forbidden

Typora使用PicGoGitee上传图片报错403 Forbidden Typora使用PicGoGitee上传图片&#xff0c;上传失败了&#xff0c;错误信息如下 打开PicGo的日志文件查看&#xff0c;可以看到错误详情如下 换了一个插件github-plus重新配置&#xff0c;解决了这个问题 再打开日志查看&…

vue+element+springboot实现多张图片上传

1.需求说明 2.实现思路 3.el-upload组件主要属性说明 4.前端传递MultipartFile数组与服务端接收说明 5.完整代码 1.需求说明 动态模块新增添加动态功能,支持多张图片上传.实现过程中对el-upload组件不是很熟悉,踩了很多坑,当然也参考过别的文章,发现处…

浅谈互联网架构演变

更好的阅读体验 \large{\color{red}{更好的阅读体验}} 更好的阅读体验 前言 可以将某个项目或产品的架构体系按照如下方式分层&#xff1a; 业务层面&#xff1a;项目业务体系技术层面&#xff1a; 数据架构&#xff1a;数据持久层策略应用架构&#xff1a;应用层的实现方式 …

HBase深度历险 | 京东物流技术团队

简介 HBase 的全称是 Hadoop Database&#xff0c;是一个分布式的&#xff0c;可扩展&#xff0c;面向列簇的数据库&#xff0c;是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案。本文会像剥洋葱一样&#xff0c;层层剥开她的心。 特点 首先我…

Android中_Service生命周期和AMS流程的创建

Service生命周期可以结合Android生命周期分析。 Service生命周期可以从两种启动Service的模式开始讲起&#xff0c;分别是context.startService()和context.bindService()。 Service的生命周期与启动和绑定状态相关。当调用startService()方法启动服务时&#xff0c;会执行onS…

65内网安全-域环境工作组局域网探针

这篇分为三个部分&#xff0c;基本认知&#xff0c;信息收集&#xff0c;后续探针&#xff0c; 基本认知 分为&#xff0c;名词&#xff0c;域&#xff0c;认知&#xff1b; 完整架构图 名词 dwz称之为军事区&#xff0c;两个防火墙之间的区域称之为dwz&#xff0c;但安全性…

大象机器人发布万元级水星Mercury人形机器人产品系列,联结未来,一触即达!

十四五机器人产业发展规划指出机器人的研发、制造、应用是衡量一个国家科技创新和高端制造业水平的重要标志。当前&#xff0c;机器人产业蓬勃发展&#xff0c;正极大改变着人类生产和生活方式&#xff0c;为经济社会发展注入强劲动能。 人形机器人作为机器人产业中重要的一环&…

【nw.js】使用nw.js将html页面打包成exe免安装程序

文章目录 一、批处理zip命令&#xff08;已有可跳过此步骤&#xff09;二、nw.js包三、使用批处理命令打包成exe可执行文件四、使用EnigmaVB打包成免安装可独立运行的exe文件五、结束 一、批处理zip命令&#xff08;已有可跳过此步骤&#xff09; 下载zip&#xff0c;你可以到该…