Java并发之volatile关键字内存可见性问题
Java并发之volatile关键字内存可见性问题
线程之间数据共享案例
我们先来看一个场景:
Main函数启动后,调用一个线程向list中添加数据。List的size为5的时候,设置变量flag为true.然后,主线程根据flag的值进行其他操作。
代码如下:
运行结果:
我们发现,当子线程输出flag为ture后,主线程也没有输出=====。
这是为什么呢?
线程在内存中运行简图
我们来看看上面程序在内存中怎么运行的
运行说明:
当程序运行的时候,先从main函数,主线程开始的,main线程先将flag=false 复制到自己程序的内存中;
这个时候开启了子线程,子线程同样将flag=false复制到自己程序内存中,在执行自己内部代码后,修改了flag的值,回写到主内存中(相对于程序自己的内存来说,内存中的数据是主内存。程序自己的内存其实就是复制了一份主内存的数据)。
如下图:
因为main线程没有刷新,没有从主线程获取最新的flag的值。所以,控制台上始终不能输出===。
结果分析
那么为什么会出现这种情况呢?【这里就需要知道两个概念:编译器和寄存器】
那是因为编译器会自动优化的结果。
编译器优化:在线程内,当读取到一个变量的时候,为了提高读写(存取)的速度,编译器在优化的时候,会先把变量读取到一个寄存器(对应上图子线程自己的内存或者是main线程自己的内存)中;以后在取这个变量的时候,就直接从寄存器中获取了;
当变量的值在本线程里面改变的时候,会同时把变量的新值同步到该寄存器中,以便保持一致;同时JVM就会向处理器发送一条指令,将这个变量所在的寄存器的值回写到系统内存(对应上图中的主内存)中。
造成数据不一致的原因:
当变量再因为别的其他线程操作而改变了值,该寄存器的值不会相应的改变,从而造成应用程序读取的值和实际的变量值不一致(如上图案例中,子线程修改了flag的值,但不会修改main线程寄存器里面的值。这个是站在变量角度来说的);
或者当该寄存器再因为别的其他线程改变了变量的值,原来变量的值不会改变,从而造成了应用程序读取的值和实际的变量值不一致(这个是从寄存器角度来说的。如上图案例中,main线程的寄存器里面是false,但是子线程已经修改成了ture).
从上面案例中,我们发现无论是主线程main函数还是子线程thread都是对变量flag进行操作的。这个时候,我们就说变量flag是线程之间共享数据了。而主内存(也就是系统内存非程序自己需要的内存)flag变量对所有共享这个变量的线程来说,都应该是可见的才可以。
那么这个时候,在Java中怎么实现线程之间共享数据的内存可见性呢?这里就是我们今天需要讲解的关键字:volatile。【ps:还有其他方案可以解决,如同步锁】
Volatile关键字
Volatile中文意思:易变的;不稳定的
Volatile关键字是一种类型修饰符,用它来声明的变量表示不可以别编译器未知因素更改。当编译器在编译过程中,遇到这个关键字声明的变量的时候,便一切都会对访问该变量的代码不再进行优化,从而可以提供特殊的地址来保证稳定访问。
通俗理解:当JVM遇到该关键字修饰的变量的时候,就会不允许编译器和处理器对指令序列进行重排(默认为了优化性能,JVM允许编译器和处理器对指令序列进行重排的)。
JVM对编译器指定的volatile规则表:
我们将flag用volatile修饰后,在运行程序,查看运行后结果:
我们可以看到,主线程输入了===,子线程也输出了当前标记为true。说明volatile起作用了。
Volatile和Synchronized 关键字的区别
1:Volatile是轻量级的同步策略;Synchronized是重量级的;
2:volatile不具备互斥性的,Synchronize是互斥的;
3:volatile不能够保证变量的原子性。
Volatile的使用场景
必备条件
在使用volatile的时候需要满足以下两个条件:
1:对变量的写操作不能依赖于当前的值。
比如上文中flag的值修改成true的时候,不受当前flag值的影响
2:该变量不能被包含在具有其他变量的不变式中。
比如int i ;y=i+1;这种情况不允许的。这个变量不能被其他变量作为不变式用。
只有在状态真正的独立于程序内其他内容的时候才可以使用volatile.
适用于场景一:状态标志
场景二:开销较低的读-写锁策略
场景三:单例中的双重校验
总结
Volatile可以解决多线程操作共享数据时候解决内存可见性问题。
简单理解:被volatile修饰的变量,编译器不会去优化调用该变量的程序(也就是不会把变量放到调用程序的寄存器中),程序调用的时候都是实时从主内存中获取到最新的数据。
凯哥个人博客:www.kaigejava.com
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里“战疫”启示录:数字化创造社会治理新价值
【金融特辑】光大银行科技部DBA女神带你从0到1揭秘MGR 数字化治理是疫情防控给社会治理带来的重要启示之一。 突如其来的疫情骤然给诸多社会场景摁下暂停键,正常运行的社会被推至应急状态,“战疫”成了一场不期而至的治理能力极限测试。 发端于浙江的健康码,在短时间内应用于全国超过200个城市的疫情防控中,成为社会治理数字化的一座里程碑;疫情期间各地紧急搭建的多条助农数字供应链,倒逼数字化治理模式的快速应用;在帮助千万中小企业快速恢复生产经营的过程中,数字经济和数字治理沉淀下来的良好基础,很好地缓冲了疫情对中小企业带来的负面影响。 偏远地区数字助农项目的落地、中小企业的线上复工复产,只是数字经济下沉趋势的一个缩影。数字化正在加速成为社会治理的新基础设施,与社会经济转型深度融合的数字化治理,也在因应社会所需的过程中创造出更多价值。 一场治理极端压力测试 不期而至的疫情是一场对治理能力的极端压力测试。 没有人会想到,口罩会在2020年年初成为最抢手的商品。海量的需求涌入电商平台,让这个日常冷门的产品,在疫情爆发的几天内卖出了几年的销量。 商家库存被扫光、工厂产能跟不上、物流因疫情受限,还有假冒伪...
- 下一篇
如何调用物联网云端API,示例说明来了!
本文档主要介绍调用物联网平台云端API的请求结构和请求示例。 请求结构 您可以通过发送HTTP或HTTPS请求调用物联网平台API。 请求结构如下: 下面以调用Pub接口向指定Topic发布消息为例: API在线调试 阿里云提供API在线调试工具 OpenAPI Explorer。在OpenAPI Explorer页,您可以快速检索和试验调用API。系统会根据您输入的参数同步生成各语言SDK的Demo代码。各语言SDK Demo显示在页面右侧示例代码页签下供您参考。在调试结果页签下,查看API调用的真实请求URL和JSON格式的返回结果。 API授权 为了确保您的账号安全,建议您使用子账号的身份凭证调用API。如果您使用RAM子账号调用物联网平台API,您需要为该RAM子账号创建、授予相应的授权策略。 为子账号授权调用API,请参见IoT API 授权映射表。 本文来自 阿里云文档中心 阿里云物联网平台 调用API 云栖号在线课堂,每天都有产品技术专家分享立即加入圈子:https://c.tb.cn/F3.Z8gvnK与专家面对面,及时了解课程最新动态!
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7