【Java面试】能说说你对volatile关键字的理解吗?

news2024/12/22 23:08:21

文章目录

  • volatile能否保证线程安全?
    • 原子性
    • 可见性
    • 有序性

volatile能否保证线程安全?

下文使用到了javap命令进行class文件的反汇编来查看字节码,如果想要了解的可以学习一下javap命令。
什么是javap命令
javap命令的参数
要解决这个问题首先要明白什么样是线程安全的。
线程安全要考虑三个方面:可见性、有序性、原子性

  • 可见性指,一个线程对共享变量修改,另一个线程能看到最新的结果
  • 有序性指,一个线程内代码按编写顺序执行
  • 原子性指,一个线程内多行代码以一个整体运行,期间不能有其它线程的代码插队

volatile 能够保证共享变量的可见性与有序性,但并不能保证原子性

原子性

这里先来解释一下什么是原子性吧。
我们知道Java的代码结果编译之后会变为class文件(字节码文件),而class文件经过JVM的解释器之后就能变为最后操作底层操作系统的机器码了。

而我们可以使用javap去查看class文件对应的字节码指令。
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息。
通过局部变量表,我们可以查看局部变量的作用域范围、所在槽位等信息,甚至可以
看到槽位复用等信息。
而经过解析之后可以发现,我们在Java中写的一行代码,其实对应了字节码中的好多行操作。如下
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

可以发现编写在Java中的一行代码对应了字节码中的好多行代码,在多线程情况下,CPU执行这些命令的时候,有可能线程分配的时间片结束了,那么这个任务就得被迫结束,然后CPU去执行其他的字节码指令。那么这个时候就有可能出现问题。
例如代码中的add方法和sub方法我使用了两个线程去执行,那么执行某一行字节码指令的时候,如果时间片结束,那么CPU执行另一个线程的操作。
此时就可能出现add方法才执行到getstatic这个字节码指令然后就被sub方法抢占,然后sub方法刚刚好执行完毕了所有的字节码指令,那么此时money的值在执行sub方法的线程中,他的值就是5,然后sub方法将数据写入到主内存的money中的时候,此时money=5,但是此时执行add方法的线程开始继续执行他的指令,他的money值还是10,然后add操作完毕之后money值等于15,那么此时就会出现数据覆盖了。

public class VolatileTest {
    private static volatile int money = 10;

    public VolatileTest() {
    }

    public static void add() {
        money += 5;
    }

    public static void sub() {
        money -= 5;
    }

    public static void main(String[] args) {
        (new Thread(() -> {
            add();
        })).start();
        (new Thread(() -> {
            add();
        })).start();
        System.out.println(money);
    }
}

在这里插入图片描述
而出现这种情况的原因就是由于,一行Java代码会被分解为多行的字节码指令,而这些字节码指令不保证原子性,也就是他们执行的过程中可以被打断,然后打断完毕之后再继续执行,那么本来的一行代码却被方块执行了。
而想要解决这种问题也很简单,就是让这个线程必须执行完毕他的所有指令的时候才能被另一个线程抢占CPU资源,那么很明显要解决这里的原子性问题,只需要使用synchronized锁即可。

可见性

具体volatile关键字是如何解决可见性问题的可以先看这篇文章
在这里插入图片描述
上图可见,每个CPU对共享变量的操作都是将内存中的共享变量复制一份副本到自己高速缓存中,然后对这个副本进行操作。如果没有正确的同步,即使CPU0修改了某个变量,这个已修改的值还是只存在于副本中,此时CPU1需要使用到这个变量,从内存中读取的还是修改前的值,这就是其中一种可见性问题。

可见性的问题在于访问共享变量的问题,例如下面的代码,如果说while中的flag变量成功被修改为true(事实上他确实成功被修改为了true),那么这里就应该输出最后一句话,而不是卡死在死循环中,说明,虽然另一个线程修改了flag的值,但是主线程好像没有读取到,这就是一个很典型的可见性问题。
在这里插入图片描述
可能你会认为其实是因为线程没有修改flag的值,所以还卡死再死循环中,那么我延迟100ms再去读取一次这个flag的值,让我们来看看到底flag是什么值。
在这里插入图片描述
可以发现是true。
那么为什么明明是true,但是主线程还是卡在死循环呢?
用下图来解释,我们知道JVM中是有一个JIT(即时编译器)的,他负责优化我们的热点代码,也就是执行次数非常多的代码,而我的while语句由于一开始是flag是false,所以再sleep的100ms时间内,其实我的while语句由于我的机器性能,可能已经执行了几百万次了,所以此时while就是热点代码,但是我每次我的while都要去主内存中读取flag的值,那么效率是很低的,因为CPU的速度是(小于)ns级别,而内存是几十ns,所以此时内存反倒成为了速度的瓶颈,所以JIT就试图优化代码,JIT发现CPU读取了几百万次的flag值都是false,所以他就直接认为flag就是false了,然后就把下图中的while条件判断直接设定为了stop=false(这里JIT直接替换了代码,所以直接flag读取都不读取了,直接while里面写的就是一个!false,所以while直接死循环),那么这样子就会导致while的条件永远为真,即使其他线程已经修改了flag的值,stop依旧继续为false,所以就会导致可见性问题。
在这里插入图片描述
JIT优化之后
在这里插入图片描述
而其他线程由于没有优化,所以其他线程依旧可以读取到flag被修改后的值。
而如果认为其实不是由于JIT导致的上面的原因,那么我们可以再运行代码的时候设定JVM参数来关闭JIT。再VM options处添加-Xint,表示禁用JIT。
在这里插入图片描述
然后继续运行一样的代码,可以发现死循环结束。
在这里插入图片描述
上面说过JIT只会优化热点代码,那么如果循环次数不够多,那么就可能不是热点代码了,我们可以试一试,可以发现循环次数减少后,JIT就没有优化代码了,不会继续死循环。
在这里插入图片描述
注意,JIT优化对代码的性能提升巨大,所以我们不可能关闭JIT。
所以我们还可以使用volatile来解决这个问题。
在这里插入图片描述

有序性

CPU会对指令进行优化,如果几条指令之间没有关联性,那么这几条指令可能他们就不会按照原有的顺序执行,而是经过CPU的排序后进行执行。
而volatile是如何解决指令重排序的呢?
他使用的是内存屏障的方式,也就是他会为volatile变量的读和写加上内存屏障,
在这里插入图片描述
例如x是再y之前声明的,而y加上了volatile关键字,那么此时x就不可能越过y的屏障,也就是对x的操作一定要先于y完成。而对于读取,那么会防止下面的读语句跑到volatile变量的读语句之前。
在这里插入图片描述

所以volatile解决有序性的话有一个要求:

volatile给写变量的时候要把写变量放在语句的最后,也就是最后给volatile变量赋值。
读取volatile变量的时候,要把读取语句放在第一句。也就是吧volatile变量拿去读取的时候应该最早读取。
也就是 写尾读头
因此volatile的使用要求还挺高的,如果没有理解内存屏障,那么可能用不明白。

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

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

相关文章

【数据库】数据库的慢查询问题

现象: 在压测过程中发现接口返回数据非常慢,甚至超时,页面加载不出数据 问题定位:如果有慢查询怎么恢复? 1.查看服务日志,打印连接数据库超时,接口返回超时 查看数据库所在节点cpu占用很高使…

夯实算法-跳跃游戏

题目:LeetCode 给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标。 示例 1: 输入: nums [2,3,1,1,4] 输出: tru…

【测试沉思录】23. 如何实现基于场景的接口自动化测试用例?

作者:陈爱娇 编辑:毕小烦 自动化本身是为了提高工作效率,不论选择何种框架,何种开发语言,我们最终想实现的效果,就是让大家用最少的代码,最小的投入,完成自动化测试的工作。 基于这…

EMQ 宣布推出 LF Edge eKuiper 全新 Logo 标识

全球领先的开源物联网数据基础设施软件供应商 EMQ 映云科技宣布,即日起,将正式启用全新的 LF Edge eKuiper(以下简称 eKuiper)产品 Logo。焕然一新的 eKuiper 产品 Logo 标志着 EMQ 在不断提升品牌全球化战略之上,对于打造高性能、高可用、高安全的世界级开源软件产品的极致追求…

关于密码设置

使用一个密码并在数据泄漏时保护自己的其它账号 关于密码 现在好多软件,好多网站都需要我们设置密码,这个时候我们的处理办法一般分为2种。 对不同的软件设置不同的密码,这种理论上是最安全的,但是记不住啊,所以不实…

Letbook Cookbook题单——数组二分与双指针

Letbook Cookbook题单——数组二分与双指针 1. 两数之和 难度:简单 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。…

Locust学习记录5-任务属性【Task】

Task 当负载测试开始时,将为每个模拟用户创建一个User类的实例,他们将开始在自己的绿色线程中运行。当这些用户运行时,他们选择他们执行的任务,休眠一段时间,然后选择一个新任务。 这些任务时普通的python可调用文件…

windows环境下python连接openGauss数据库

文章目录一、python 介绍二、Python下载及安装三、openGauss Connectors (Psycopg2) 介绍四、openGauss Connectors (Psycopg2)下载并初始化五、连接并访问openGauss数据库六、问题总结(FAQ)一、python 介绍…

Maven学习笔记

Maven Maven是一个项目管理工具依赖管理 传统工程中我们直接把jar包放在项目中maven工程真正的jar包放在仓库中,项目中只防止jar包的坐标 一键构建 maven自身集成了tomcat插件,可以对项目进行编译、测试、打包、安装、发布等操作 仓库的种类 本地仓库远程…

计算机毕业论文java毕业设计选题源代码ssm校园兼职系统|求职招聘系统

💖💖更多项目资源,最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 3.1 需求分析 校园兼职系统主要是为了提高工作人员的工作效率和更方便快捷的满足用户,更好存储所有数据信息及快速方便的检索功能&am…

人工智能与机器学习

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 人工智能与机器学习📝人工智能相关概念☞什么是人工智能、机器学习、深度学习☞人工智能发…

思科防火墙NAT——实验

作者简介:一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 1.实验 防火墙配置 其他配置 前言 本章将会讲解思科NAT配置的…

记一次 .NET 某工控软件 内存泄露分析

一:背景 1.讲故事 上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也没分析出来,…

Hash与ZSet的常用命令以及其底层数据结构

目录hash类型命令hsethgethmsethmgethgetallhsetnxhdelhexitshincrby与hincrbyfloathkeys与hvalshlenhstrlen有序set型命令zaddzrange 与zrevrangezrangebyscore与zrevrangebyscorezcardzcountzscorezincrbyzrank 与 zrevrankzremzremrangebyrankzremrangebyscorezrangebylexzl…

MySQL学习笔记(十四)索引失效有哪些情况?

1.表和数据 CREATE TABLE t_user (id bigint(32) NOT NULL AUTO_INCREMENT,user_name varchar(40) DEFAULT NULL COMMENT 用户名,user_code varchar(40) DEFAULT NULL COMMENT 用户编号,phone varchar(11) DEFAULT NULL COMMENT 电话,age tinyint(3) DEFAULT NULL COMMENT 年龄…

微机----------------中断技术

目录 中断概述中断处理过程中断请求中断响应中断处理8086/8088中断中断类型中断优先级顺序⭐中断向量表中断处理过程中断概述 中断: 当CPU执行程序时,由于随机的事件引起CPU暂时停止正在执行的程序,而转去执行中断服务程序,处理完后又返回被终止的程序断点处继续执行,这个…

Bug系列路径规划算法原理介绍(四)——I-BUG 算法

本系列文章主要对Bug类路径规划算法的原理进行介绍,在本系列的第一篇文章中按照时间顺序梳理了自1986年至2018年Bug类路径规划算法的发展,整理了13种BUG系列中的典型算法,从本系列的第二篇文章开始依次详细介绍了其中具有代表性的BUG1、BUG2、…

数学基础从高一开始2、集合间的基本关系

高中数学人教 A 版必修一 集合间的基本关系 学习目标: (1)初步理解集合之间的包含与相等的含义; (2)能识别给定集合的子集和真子案,了解空集含义: (3)能进行自然语言、图形语言(Venn 图)、符号语言闻的转换,积萦抽象思维的经验, …

递归算法(及其衍生算法:缓存,分治,回溯)

文章目录一、初识递归二、缓存三、分治四、回溯一、初识递归 递归函数 终止条件 递归关系 终止条件: 当大问题被拆解成能轻松解决的小问题时,运行终止条件中的逻辑 递归关系: 定义如何将大问题拆解为小问题 例子:小名跑步。 …

这十一个副业在家就可以完成,疫情在家也有收入,建议收藏

2022年,谁还没有副业? 经过两年的疫情,我们都知道没有钱是一件非常不舒服的事情。现在的做法是:主营业务要求稳定,副业要求发展;好好发展副业是硬道理。 在过去的两年里,我一直在探索副业项目…