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

编辑

Java并发之volatile关键字内存可见性问题的更多相关文章

  1. java多线程 -- volatile 关键字 内存 可见性

    内存可见性(Memory Visibility) 1 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其 ...

  2. 二、volatile关键字 - 内存可见性

    1.内存可见性 ​ (程序在运行时,jvm会为每一个执行任务的线程都分配一个独立的缓存,用于提高效率) ​ 我觉得可以这样来理解: ​ 内存:啥是内存?就是可以理解成电脑当中的内存条,程序创建个变量, ...

  3. Volatile 关键字 内存可见性

    1.问题引入 实现线程: public class ThreadDemo implements Runnable { private boolean flag = false; @Override p ...

  4. Java并发之volatile关键字

    引言 说到多线程,我觉得我们最重要的是要理解一个临界区概念. 举个例子,一个班上1个女孩子(临界区),49个男孩子(线程),男孩子的目标就是这一个女孩子,就是会有竞争关系(线程安全问题).推广到实际场 ...

  5. 1.volatile关键字 内存可见性

    Java JUC 简介 在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括 ...

  6. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  7. Java 高效并发之volatile关键字解析

    摘录 1. 计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执 ...

  8. Java 并发:volatile 关键字解析

    摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...

  9. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  10. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

随机推荐

  1. Spring中文官方文档

    Spring 中文文档 https://springdoc.cn/ Spring Boot 中文文档 https://www.docs4dev.com/docs/zh/spring-boot/1.5. ...

  2. Oracle 存储过程 捕获异常

    1.带参数插入并带返回值,异常信息 CREATE OR REPLACE PROCEDURE test_pro (v_id in int,v_name in varchar2,app_code out ...

  3. 机器学习策略篇:详解数据分布不匹配时,偏差与方差的分析(Bias and Variance with mismatched data distributions)

    详解数据分布不匹配时,偏差与方差的分析 估计学习算法的偏差和方差真的可以帮确定接下来应该优先做的方向,但是,当训练集来自和开发集.测试集不同分布时,分析偏差和方差的方式可能不一样,来看为什么. 继续用 ...

  4. 【实操记录】MySQL二进制安装包部署

    截至2023年11月2日,MySQL社区版最新版本是8.0.35,本文详细描述了采用二进制安装的各个步骤,具有较强的参考意义,基本可作为标准步骤实施. ■ 下载数据库介质 社区版的下载地址为oracl ...

  5. ECMA标准ECMAScript(JavaScript的一个标准)和C#

    2024 年 6 月 26 日,第 127 届 ECMA 大会正式批准了 ECMAScript 2024 语言规范,这意味着它现在正式成为最新 ECMAScript 标准.ECMAScript是ECM ...

  6. RS232转PN协议网关模块连接PLC和霍尼韦尔条码扫描器通信

    为了更快地输入信息,许多设备都配备了条码扫描器,但条码扫描器不能直接与CPU通信.最直接和方便的方法是加RS232转PN协议网关模块(BT-PNR20).本文将深入研究如何利用巴图自动化的RS232转 ...

  7. oeasy教您玩转vim - 77 - # 保留环境viminfo

    ​ 保留环境viminfo 回忆组合键映射的细节 上次我们定义了session :mks 还可以加载会话session :source Session.vim vim -S Session.vim 基 ...

  8. TIER 0: Dancing

    TIER 0: Dancing SMB Server Message Block 是一种网络协议,用于在计算机网络上共享文件.打印机和其他资源.它最初由微软开发,用于在 Windows 操作系统之间进 ...

  9. SpringBoot+ Sharding Sphere 轻松实现数据库字段加解密

    一.介绍 在实际的软件系统开发过程中,由于业务的需求,在代码层面实现数据的脱敏还是远远不够的,往往还需要在数据库层面针对某些关键性的敏感信息,例如:身份证号.银行卡号.手机号.工资等信息进行加密存储, ...

  10. 【ue源码】定制-蓝图部分

    今天在更新UE的伤害系统的时候出现了一个问题: 多个地方频繁调用一个函数,而这个函数肯定做优化,具体就是,把对应数据放入到队列,另外一个地方慢慢消费这个队列, 那么问题出现了,我使用的UE只有Sing ...