说volatile之前,了解JMM(Java内存模型)有助于我们理解和描述volatile关键字。JMM是Java虚拟机所定义的一种抽象规范,用来屏蔽不同硬件和操作系统的内存访问差异,让Java程序在各种平台下都达到一致的内存访问效果。JMM也可以称之为Java线程内存模型,也描述了Java线程在工作中对数据的操作过程以及描述了线程之间的通信过程。

  以上便是JMM的基本逻辑图,Java采用工作内存和主内存进行数据交互的原因可以解释为,工作内存一般为cpu的高速缓存,cpu的高速缓存就是为了解决cpu日益增长的速度与主存不匹配导致浪费计算资源,所以线程的工作内存位于cpu的高速缓存中来提高运算速度。但是多个线程在对主内存中共享变量操作时会有一个可见性问题。具体可看以下代码:

package xyz.ring2.demo.test;

public class VolatileVisibilityTest {
public static boolean flag = false; public static void changeCondition(){
flag = true;
} public static void main(String[] args) throws InterruptedException {
System.out.println("working and waiting for change...");
new Thread(new Runnable() {
@Override
public void run() {
while (!flag){
System.out.println("hello");
}
}
}).start(); Thread.sleep(200); new Thread(new Runnable() {
@Override
public void run() {
changeCondition();
System.out.println("condition has changed.");
}
}).start(); Thread.sleep(200);
System.out.println("work done."); } }

  在该程序中有一个共享变量flag,第一个线程运行时等待别的线程改变flag的值使其跳出循环,第二个线程是去改变共享变量flag的值。在我们看来,第一个线程只需要等待第二个线程改变了flag的即可跳出循环。以下是程序运行结果:

  可以看到当“work done”打印出来时程序还没有停止,此时我们可以得出结论。两个线程对共享变量的操作是互相不可见的。此时我们很自然的想到了通过加synchronizedJava内置锁来解决。

通过在while循环外添加synchronized(this)同步块确实能解决这种问题,但是在这种仅仅只需要保证一个共享变量可见的情况下采用synchronized锁来保证同步代价太大,此时我们应该采用Java所

提供的volatile关键字来保证变量的可见性。使用上通过在flag前加上volatile关键字即可。

public static volatile boolean flag = false;

以下是运行结果:

正常的使程序结束了,线程一成功的感知到了线程二对flag变量的改变。

  那么volatile关键字使如何保证多线程下共享变量线程间可见的呢?

首先我们来了解以下JMM中的数据原子操作:

  • read(读取):从主内存读取数据
  • load(载入):将主内存读取到的数据写入工作内存
  • use(使用):从工作内存读取数据来计算
  • assign(赋值):将计算好的值从新赋值到工作内存中
  • store(存储):将工作内存数据写入到主内存
  • write(写入):将store过去的变量值赋值给主内存中的变量
  • lock(锁定):将主内存变量加锁,标识为线程独占状态
  • unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量

  JVM通过以上原子操作来处理主内存和工作内存中的数据交互。那么volatile到底是如何保证的呢?

  Java中的volatile关键字是通过调用C语言实现的,而在更底层的实现上,即汇编语言的层面上,用volatile关键字修饰后的变量在操作时,最终解析的汇编指令会在指令前加上lock前缀指令

来保证工作内存中读取到的数据是主内存中最新的数据。具体的实现原理是在硬件层面上通过:MESI缓存一致性协议:多个cpu从主内存读取数据到高速缓存中,如果其中一个cpu修改了数据

,会通过总线立即回写到主内存中,其他cpu会通过总线嗅探机制感知到缓存中数据的变化并将工作内存中的数据失效,再去读取主内存中的数据。

 IA32架构软件开发者手册对lock前缀指令的解释:
  1.会将当前处理器缓存行的数据立即回写到系统内存中,
  2.这个写回内存的操作会引起其他cpu里缓存了该内存地址的数据失效(MESI协议)

  现在我们知道了volatile可以保证变量的可见性,我们还应该知道volatile不可以保证原子性:

  volatile无法保证原子性:如:两个线程同时read主内存中相同的值,load到工作内存中,两个线程的cpu又同时use了count值并进行了计算且assign回工作内存,但其中一个线程通过总线store回主内存的
  速度更快,于是由于(总线)MESI缓存一致性协议下的cpu总线嗅探机制就会使得另一个线程工作内存中的变量副本失效,导致之前的操作结果丢失(可以结合图片理解)。

  并发编程的三大特性:可见性,原子性,有序性。那么volatile对有序性又是怎样的呢。。。这涉及到happens-before规则,volatile关键字可以体统屏障保护,使得编译器和jvm对变量操作的重排序失效。

可以读取我的另一篇文章:单例模式值双检索 来理解一下重排序所带来的问题。

一文搞懂volatile的可见性原理的更多相关文章

  1. 一文搞懂一致性hash的原理和实现

    在 go-zero 的分布式缓存系统分享里,Kevin 重点讲到过一致性hash的原理和分布式缓存中的实践.本文来详细讲讲一致性hash的原理和在 go-zero 中的实现. 以存储为例,在整个微服务 ...

  2. 一文搞懂 Prometheus 的直方图

    原文链接:一文搞懂 Prometheus 的直方图 Prometheus 中提供了四种指标类型(参考:Prometheus 的指标类型),其中直方图(Histogram)和摘要(Summary)是最复 ...

  3. Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

    本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...

  4. 一文搞懂所有Java集合面试题

    Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...

  5. 一文搞懂指标采集利器 Telegraf

    作者| 姜闻名 来源|尔达 Erda 公众号 ​ 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...

  6. 一文搞懂RAM、ROM、SDRAM、DRAM、DDR、flash等存储介质

    一文搞懂RAM.ROM.SDRAM.DRAM.DDR.flash等存储介质 存储介质基本分类:ROM和RAM RAM:随机访问存储器(Random Access Memory),易失性.是与CPU直接 ...

  7. 基础篇|一文搞懂RNN(循环神经网络)

    基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...

  8. 一文搞懂vim复制粘贴

    转载自本人独立博客https://liushiming.cn/2020/01/18/copy-and-paste-in-vim/ 概述 复制粘贴是文本编辑最常用的功能,但是在vim中复制粘贴还是有点麻 ...

  9. 三文搞懂学会Docker容器技术(中)

    接着上面一篇:三文搞懂学会Docker容器技术(上) 6,Docker容器 6.1 创建并启动容器 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] --na ...

随机推荐

  1. Jenkins(2)- 更改插件源为国内源

    如果想从头学起Jenkins的话,可以看看这一系列的文章哦 https://www.cnblogs.com/poloyy/category/1645399.html jenkins插件清华大学镜像地址 ...

  2. Linux系统管理第四次作业 磁盘管理 文件系统

    1.为主机新增两块30GB的SCSI硬盘 2.划分3个主分区,各5GB,剩余空间作为扩展分区 [root@localhost ~]# fdisk /dev/sdb 欢迎使用 fdisk (util-l ...

  3. 【集群实战】inotify

    1. inotify简介 Inotify是一种强大的,细粒度的,异步的文件系统事件监控机制(软件). linux内核从2.6.13起,加入了Inotify支持,通过Inotify可以监控文件系统中添加 ...

  4. material UI中子组件样式修改的几种方案研究

      material UI是一个流行的与React配套的前端UI框架,对于开发者而言,熟悉它的样式修改方案是必要的.但目前相关资料并不直观,并且没有总结到一起.如果对相关特性不太清楚,开发者很可能会在 ...

  5. Galera将死——MySQL Group Replication正式发布

    2016-12-14 来源:InsideMySQL 作者:姜承尧 MySQL Group Replication GA 很多同学表示昨天的从你的全世界路过画风不对,好在今天MySQL界终于有大事情发生 ...

  6. Mark一篇介绍Java垃圾回收和JVM参数设置的文章

    贴出原文连接:重磅!Java 内存管理白皮书,读完它,java 内存管理的问题完全 NO Problem! 读了一遍,对并行的垃圾回收还不是很理解,先mark,消化消化再学习. 文章说的一些JVM设置 ...

  7. 解决 Retrofit 多 BaseUrl 及运行时动态改变 BaseUrl ?

    原文地址: juejin.im/post/597856- 解决Retrofit多BaseUrl及运行时动态改变BaseUrl(一) 解决Retrofit多BaseUrl及运行时动态改变BaseUrl( ...

  8. double运算的坑

    某个结果运算后,得出的数据:a = 15.599999999 而不是15.6,导致条件判断 a < 15.6 为true,使程序出现bug 解决办法,对运算后的浮点数,进行格式化(以保留一位小数 ...

  9. inotify-tools的inotifywait工具用exclude 和 fromfile 排除指定后缀文件

    今天打算使用 inotify-tool 来对线上程序文件进行监控, 因为有些目录是缓存目录, 所以要进行排除, 同时还要排除一些指定的后缀的文件, 比如 .swp 等 需要递归监控的目录为: /tmp ...

  10. INTERVIEW #5

    笔试 150min,3题,每题100分,自己果然还是个蒟蒻呢~ 最近状态好差,虽然做了一些题,但还是考得稀烂,大概有几点需要加强: 独立做题,不要一边看板子一边写代码,更不要一开始就看题解: 对之前研 ...