如果下面两段代码你能清楚的知道是为什么,那么就没有必要看本文了

代码示例1:下面的代码不会打印出“粮粮”

public class Act {static int a = 0;public static void main(String[] args) throws InterruptedException {new Thread(() -> {Thread.sleep(1000);// 省略try catcha = 1;}).start();while (a == 0) {}System.out.println("粮粮");}
}

代码示例2:下面的代码打印出“粮粮”

public class Act {static int a = 0;public static void main(String[] args) throws InterruptedException {new Thread(() -> {Thread.sleep(1000);// 省略try catcha = 1;}).start();while (a == 0) {//同示例1相比,只多出了这一行代码System.out.println();}System.out.println("粮粮");}
}

本文对于C语言的朋友来说可能比较容易阅读,但是对于只会Java的朋友来说稍微有些复杂,这篇AT&T拓展内联汇编(ATT/GCC)会让你更好的理解本文,当然了,不看也可以。。。

Java中volatile关键字的的作用是每次都能获取最新的变量(主存)数据,很明显,虽然上述2个示例中我没有使用volatile关键字,但是在示例2中,也依然达到了volatile的效果,本文要论述的问题就是示例2的底层本质,虽然本文标题是volatile如何实现,但文章内容更倾向于内存屏障,这也包含了volatile的在hotspot中的实现原理,所以我将本文标题定义为volatile实现

Load与Store
Load:将内存中的数据放到寄存器
Store:将寄存器中的数据放到内存

由于JVM是基于栈的虚拟机,所以JVM会使用栈来模拟寄存器,在java中叫做操作数栈,用栈帧中的局部变量表来模拟物理机器的内存,所以我们也可以说
Load操作 :从一个对象的成员变量中读取值
Store操作:往一个对象的成员变量中写入值

下面的代码解释了什么是Load和Store

public class Demo1{int a;//成员变量public void m1() {int p = a;//Load  aa = 666;  //Store a}
}

编译器重排序
下面的示例方法m1中,对于编译器来说,先执行p1 = a,还是先执行p2 = b,在单线程下语义都是一样的,所以编译器编译出来的文件有可能先执行p2 = b,再执行p1 = a,这种现象就叫做编译器重排序

代码示例3
public class Demo1{int a;//成员变量int b;//成员变量public void m1() {int p1 = a;//Load a操作int p2 = b;//Load b操作}
}

CPU重排序
CPU为了流水线饱和,很有可能同编译器一样也会进行重排序的操作,也就是说,对于上述代码,即使编译器不重排序,CPU也有可能进行重排序

内存屏障
那么如何禁止编译器重排序呢?使用一个叫做内存屏障的东西,内存屏障就像一堵墙,屏障两边的代码可以互相排序,但是屏障一侧的代码,不能排序到另一侧,其实本应该分两种情况来说,1种是编译器屏障,在hotspot中使用AT&T语法的拓展内联汇编的volatile关键字来实现,另1种是内存屏障,在hotspot中的拓展内联汇编中加入lock前缀,这两种看不明白没关系,总之记住一句话,能防止两部分代码重排序的方式,就叫做内存屏障,hotspot抽象出了4种屏障,请看下面代码中的注释部分

public class Demo1{int a;int b;public void m1() {int p1 = a;//Load a操作// 如果我在此处加一行代码,能导致先执行a,再执行b,那么这行代码就叫做内存屏障int p2 = b;//Load b操作}
}

LoadLoad屏障
上述代码如果在p1 = a和p2 = b之间加点东西,那么此时这个东西就叫做内存屏障,由于p1 = a和p2 = b都是Load操作,所以这种屏障就叫做LoadLoad屏障,下面的代码是稍微修改之后的代码

public class Demo1{int a;int b;public void m1() {int p1 = a;//Load a操作如果在此行插入屏障,则该屏障叫做LoadLoad屏障int p2 = b;//Load b操作}
}

如果想要禁止重排序,得加上一个LoadLoad屏障,那么具体如何加呢??在Java语言的层次上,是Unsafe类的fullFence,当然了,程序员无法直接使用该类,而且该类在java9中被其他类替代了,具体是什么我也没研究,总之,能明白这个意思就行,因为对于上述示例来说,真的没有必要禁止重排序,内存屏障主要的使用者是编译器,而不是程序员,后文会给出解释和示例

LoadStore屏障
同理,除了LoadLoad,还有LoadStore,StoreStore,StoreLoad屏障,下面的代码如果想要禁止重排序,则需要添加LoadStore屏障

public class Demo1{int a;int b;public void m1() {int p1 = a;//Load a操作如果在此行插入屏障,则该屏障叫做LoadStore屏障b=666;     //Store b操作}
}

使用屏障的场景
到目前为止,上文虽然表达了什么是屏障,但是只是为了更好的解释什么是内存屏障,并不是真正的使用场景,因为上述代码即使不使用屏障也没有丝毫问题,那么在java中,到底满足哪些条件,才会使用屏障呢?在hotspot中,如果两个挨着的操作,满足如下关系,则会被自动插入内存屏障

根据上表规则可知,如下代码在编译时必定会被插入内存屏障

public class Demo{volatile int v1;int a;public void m1() {int p1 = v1;//Load v1,这个操作也叫读取volatile成员v1这里会被插入LoadStore内存屏障a=666;      //Store a操作,这个操作也叫写入普通成员a}
}

到此为止,这也真正说明了为什么会说volatile有禁止重排序的效果,其实它的本质是编译器根据自己的规则,而这个规则波及到了被volatile修饰的成员变量,所以有人说volatile会禁止重排序,通过上述规则,那么我们可以大胆的推测出下面的代码也会被插入内存屏障

public class Demo{volatile int v1;int a;public void m1() {synchronized (this) {// 进入synchronized(线程进入监视区)这里会被插入LoadStore内存屏障 a = 666; // Store a操作,也叫写入普通成员a} // 退出synchronized(线程退出监视区)这里会被插入StoreLoad内存屏障 a = v1; // Load v1操作,也叫读取volatile成员v1}
}

内存屏障如何实现的?
4中内存屏障,LoadLoad,LoadStore,StoreStore,StoreLoad的实现方式是相同的,它们的源代码是GCC的AT&T内联汇编写的,定义如下

asm volatile ("":::"memory");

没错,如你所想,这行代码就是内存屏障,重点在于它不仅有内存屏障的功能,它还有清空寄存器的语义,"memory"表示每次读取都从主存(而非寄存器)读取数据,所以有内存屏障的地方,读取的数据都是最新的数据,回想本文开头的代码示例1和代码示例2,发现示例2会有volatile的效果,最终答案就是System.out.println方法里面有synchronized块,所以会出现内存屏障,而出现内存屏障,就会从主存读取数据,从而达到获取最新值的效果

Java-volatile是如何实现的相关推荐

  1. Java volatile关键字原理解剖

    Java volatile关键字原理解剖 文章目录 Java volatile关键字原理解剖 参考文章 前置知识 CPU缓存模型 CPU缓存行 并发编程基本概念 Java锁概念 volatile关键字 ...

  2. [Java并发编程(三)] Java volatile 关键字介绍

    [Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...

  3. Java Volatile关键字可见性问题分析

    Java Volatile关键字可见性问题分析 Java 内容模型 普通变量(非Vola变量)的内存不可见性 Volatile变量的内存可见性 剩余疑惑 Java 内容模型 具体可以查看这篇文章Jav ...

  4. volite java_如何理解 JAVA volatile 关键字

    最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂.所以学习过程中顺手翻译下来,一方面巩固知识,一方面希 ...

  5. Java Volatile 详解

    Java Volatile 详解 Volatile:是java虚拟机提供的轻量级的同步机制.保证可见性.禁止指令重排序.不保证原子性!!! 学习Volatile之前必须了解JAVA内存模型. Java ...

  6. java volatile原理A CUP层面

    作者:知乎用户 链接:https://www.zhihu.com/question/49656589/answer/117826278 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业 ...

  7. Java Volatile keyword

    java的volatile是什么意思 我们知道,在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步. 这在JVM 1. ...

  8. 一个具体的例子学习Java volatile关键字

    相信大多数Java程序员都学习过volatile这个关键字的用法.百度百科上对volatile的定义: volatile是一个类型修饰符(type specifier),被设计用来修饰被不同线程访问和 ...

  9. java volatile 用法_java关键字volatile用法详解

    volatile关键字想必大家都不陌生,在java 5之前有着挺大的争议,在java 5之后才逐渐被大家接受,同时作为java的关键字之一,其作用自然是不可小觑的,要知道它是java.util.con ...

  10. java volatile 多线程_Java多线程之volatile

    在学习Volatile之前有必要简单了解一下物理内存模型和Java的内存模型,这样对理解Volatile大有好处. 寄存器 首先我们要知道的是所有运算操作都是在CPU的寄存器中进行的,而CUP的执行涉 ...

最新文章

  1. 当下火热的大数据视频,免费送(含源码)
  2. 一、预备知识―程序的内存分配
  3. svn trunk branches tags 的用法
  4. js获取节点的DOM操作
  5. 吴恩达深度学习 —— 2.4 梯度下降
  6. 希尔排序的详细过程_算法系列: 10大常见排序算法(4)希尔排序
  7. Target-Action回调模式
  8. MPEG-7实例入门
  9. 基于react做了一个仿qq空间
  10. 现在被apihook搞郁闷了.....进展很慢...先放上几个必用的api说明吧.
  11. python程序题求roc-auc是一种常用的模型评价指标_【Python机器学习 5-3】模型评价指标及模型选择...
  12. kali使用jd-gui
  13. Java web speach api_HTML5 Web Speech API,让网站更有趣
  14. oracle数据库在mybatis中的数值类型(NUMBER型)
  15. 我们真的需要一部《数据安全法》
  16. HTML我的家乡宁夏学生网页设计作品 dreamweaver作业静态HTML网页设计模板 宁夏旅游景点网页作业制作...
  17. 手机搜狐概念版 html,搜狐领跑四大门户 首推H5技术手机概念版
  18. Oracle EBS 动态调用 XML Publisher 模板 输出不同的报表
  19. 游戏服务器 协议 安全问题,游戏服务器开发安全问题
  20. Java IO流之装饰模式与适配器模式讲解

热门文章

  1. MySQL解析json字符串的相关问题
  2. oracle 多版本技术,读书笔记: 关于oracle中多版本的问题
  3. php-fpm启动条件,php-fpm的启动、重启
  4. IDEA2018部署jeesite3完美运行教程
  5. The driver is automatically registered via the SPI and manual loading of the
  6. 获取和使用某些网站的iconfont图标字体
  7. 微服务的通信协议:Restful,RPC(Dubbo、Motan、gRPC)
  8. java十进制转换成二进制数
  9. linux文件替换命令sed使用
  10. SQL Server监控全解析