说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. thinkphp--导入导出xls文件

    /** * 数组转xls格式的excel文件 * @param array $data 需要生成excel文件的数组 * @param string $filename 生成的excel文件名 * 示 ...

  2. DEDE Fatal error: Maximum execution time of 30 seconds exceeded 致命 错误: 最大的 执行 时间 为 30 秒

    刚安的DEDE    5.7 -SP1-GBK的  为何一登录后台点任何链接都显示超过30秒  后台假死 网上搜的方法一般都是更改执行时间上限,其目的是为了解决一些大的数据,真的需要30秒以上的执行时 ...

  3. 2019-2020-1 20199326《Linux内核原理与分析》第四周作业

    第四周学习内容 庖丁解牛Linux内核分析第三章:MenuOS的构造 Linux内核分析实验三 学到的一些知识 操作系统两大宝剑:1.中断上下文的切换--保存现场和恢复现场 2.进程上下文的切换 Li ...

  4. Spring Boot的TestRestTemplate使用

    文章目录 添加maven依赖 TestRestTemplate VS RestTemplate 使用Basic Auth Credentials 使用HttpClientOption 使用RestTe ...

  5. Scala的自定义类型标记

    Scala的自定义类型标记 Scala中有很多千奇百怪的符号标记,看起来是那么的独特,就像是一杯dry martini-好像黑夜中的萤火虫,那么耀眼,那么出众. 好了言归正传,这一篇文章我们会讲一下S ...

  6. 图论--DFS总结

    1.Key word:①双向DFS  ②回溯 今天就看到了这么多DFS,其实DFS更倾向于枚举所有情况. 对于双向DFS,我们考虑看看最短路,起点做一下搜索,记录一下到所有点的距离,终点做一下搜索,记 ...

  7. P2620 虫洞

    题目背景 applepi 想进行宇宙旅行.当然,applepi 知道这是有可能的,因为applepi 的特殊能力能使他观测到宇宙中的虫洞.所谓虫洞就是一个在三维之外的维度打开的快捷通道,通过虫洞能够从 ...

  8. Codeforces Round #637 (Div. 2) 题解

    A. Nastya and Rice 网址:https://codeforces.com/contest/1341/problem/A Nastya just made a huge mistake ...

  9. Java——接口相关知识

    1.接口用interface来声明 //定义一个动物接口 public interface Animal{ public void eat(); public void travel(); } 2.接 ...

  10. 揭露.net培训结构软谋收钱踢学员的套路

    本人以下文章全部真实,希望管理员能通过,给更多的.net学者一个警示,避免更多的.neter掉入泥坑. 本人小码农一枚,主要做.net方向,苦于进步无门,各种资料收集渠道受限,最后狠心花一个月工资报名 ...