volatile的特性:

  1. volatile可见性:对一个volatile的读,总可以看到对这个变量最终的写;
  2. volatile原子性:volatile对单个读/写具有原子性(32位Long、Double),但是复合操作除外,例如:i++;
  3. jvm底层采用“内存屏障”来实现volatile语义。

volatile的内存语义及实现:
  在JMM中,线程之间的通信采用共享内存来实现的。
volatile内存语义是:

    • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中;
    • 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。

volatile的底层实现是通过插入内存屏障,但是对于编译器来说,发现一个最优布置来最小化插入内存屏障的总数几乎是不可能的,所以,JMM采用保守策略。如下:

  • 在每一个volatile写操作前面插入一个StoreStore屏障
  • 在每一个volatile写操作后面插入一个StoreLoad屏障
  • 在每一个volatile读操作后面插入一个LoadLoad屏障
  • 在每一个volatile读操作后面插入一个LoadStore屏障

StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中;
StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序
LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序
LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序

java中volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

在java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

    1. volatile关键字会禁止指令重排;
    2. 2、synchronized关键字保证同一时刻只允许一条线程操作。 synchronized是万能,他可以同时满足三种特性,这其实也是很多人滥用synchronized的原因。

volatile实现原理

1)JMM把内存屏障指令分为下列四类:
StoreLoad 
Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer 
fully flush)。
Store:数据对其他处理器可见(即:刷新到内存)
Load:让缓存中的数据失效,重新从主内存加载数据 
2)JMM针对编译器制定的volatile重排序规则表
是否能重排序 第二个操作 
第一个操作 普通读/写 volatile读 volatile写 
普通读/写     NO 
volatile读 NO NO NO 
volatile写   NO NO 
举例来说,第三行最后一个单元格的意思是:在程序顺序中,当第一个操作为普通变量的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作。
从上表我们可以看出:
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 
* 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。 
JMM内存屏障插入策略(编译器可以根据具体情况省略不必要的屏障):
* 在每个volatile写操作的前面插入一个StoreStore屏障。 
*  对于这样的语句Store1 StoreStore Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见 
* 在每个volatile写操作的后面插入一个StoreLoad屏障。 
* 对于这样的语句Store1 StoreLoad Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见 
* 在每个volatile读操作的后面插入一个LoadLoad屏障。 
* 对于这样的语句Load1 LoadLoad Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕 
* 在每个volatile读操作的后面插入一个LoadStore屏障。   
* 对于这样的语句Load1 LoadStore Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕 
volatile保证可见性
  volatile修饰的变量写之后将本地内存刷新到主内存,保证了可见性
volatile保证有序性
  volatile变量读写前后插入内存屏障以避免重排序,保证了有序性
volatile不保证原子性
  volatile不是锁,与原子性无关
要我说,由于CPU按照时间片来进行线程调度的,只要是包含多个步骤的操作的执行,天然就是无法保证原子性的。因为这种线程执行,又不像数据库一样可以回滚。如果一个线程要执行的步骤有5步,执行完3步就失去了CPU了,失去后就可能再也不会被调度,这怎么可能保证原子性呢。
为什么synchronized可以保证原子性 ,因为被synchronized
修饰的代码片段,在进入之前加了锁,只要他没执行完,其他线程是无法获得锁执行这段代码片段的,就可以保证他内部的代码可以全部被执行。进而保证原子性。

volatile不保证原子性的例子:

/** * 创建10个线程,然后分别执行1000次i++操作。目的是程序输出结果10000 *
但是,多次执行的结果都小于10000。这其实就是volatile无法满足原子性的原因。*/
public class Test {
public volatile int inc = 0; public void increase() {
inc++;
} public static void main(String[] args) {
final Test test = new Test();
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++)
test.increase();
};
}.start();
}
while (Thread.activeCount() > 1) // 保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}

参考:volatile的实现原理

参考:volatile的工作原理

volatile的工作原理的更多相关文章

  1. 认识volatile的工作原理

    前言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”.可见性的意思是当 ...

  2. java面试题之volatile的工作原理

    volatile的特性: volatile可见性:对一个volatile的读,总可以看到对这个变量最终的写: volatile原子性:volatile对单个读/写具有原子性(32位Long.Doubl ...

  3. Linux内核设计第二周——操作系统工作原理

    Linux内核设计第二周 ——操作系统工作原理 作者:宋宸宁(20135315) 一.实验过程 图1 执行效果 从图中可以看出,每执行my_ start_ kernel函数两次或一次,my_ time ...

  4. Nagios工作原理

    图解Nagios的工作原理 Nagios的主动模式和被动模式 被动模式:就如同上图所显示的那样,客户端起nrpe进程,服务端通过check_nrpe插件向客户端发送命令,客户端根据服务端的指示来调用相 ...

  5. 就是要你懂Java中volatile关键字实现原理

    原文地址http://www.cnblogs.com/xrq730/p/7048693.html,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是j ...

  6. Java 8 中 ConcurrentHashMap工作原理的要点分析

    简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了对不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这 ...

  7. Java8 中 ConcurrentHashMap工作原理的要点分析

    简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些 ...

  8. 深入理解AsyncTask的工作原理

    一.为什么需要工作者线程 我们知道,Android应用的主线程(UI 线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免“用户点击按钮后没反应”这样的糟糕用户体验,我们就要确保主线程时刻保持着 ...

  9. 2017.7.11 fuse工作原理

    FUSE的工作原理如图所示.假设基于FUSE的用户态文件系统hello挂载在/tmp/fuse目录下.当应用层程序要访问/tmp/fuse下的文件时,通过glibc中的函数进行系统调用,处理这些系统调 ...

随机推荐

  1. 使用excel计算骰子输赢概率

    如何得到使用3个骰子掷赢4个骰子的概率(每个骰子的点数为1-6,点数一样算输) 分为3步解决: 第一步.计算n个骰子得到m点数的分布 1个骰子能得到1.2.3.4.5.6点数,每个点数出现的方式只有1 ...

  2. http测试工具

    http测试工具: https://github.com/denji/awesome-http-benchmark wrk https://github.com/wg/wrk wrk2 https:/ ...

  3. Multiple dex files define Lokhttp3/internal/wsWebSocketProtocol

    Multiple dex files define Lokhttp3/internal/wsWebSocketProtocol 老套路,先晒图 图一:如题,在编译打包时遇到了如上错误,很明显这是一个依 ...

  4. Django安装于基本介绍

    pycharm果然是最强大的python IDE,在创建Diango项目时如果没有则自动下载. 然而即使是这样,我在安装Django的时候还是比较曲折的. Django的安装方式有很多,但是因为网络问 ...

  5. tensorflow学习笔记——多线程输入数据处理框架

    之前我们学习使用TensorFlow对图像数据进行预处理的方法.虽然使用这些图像数据预处理的方法可以减少无关因素对图像识别模型效果的影响,但这些复杂的预处理过程也会减慢整个训练过程.为了避免图像预处理 ...

  6. 数据仓库系列之ETL过程和ETL工具

    上周因为在处理很多数据源集成的事情一直没有更新系列文章,在这周后开始规律更新.在维度建模中我们已经了解数据仓库中的维度建模方法以及基本要素,在这篇文章中我们将学习了解数据仓库的ETL过程以及实用的ET ...

  7. REST 在 Java 中的使用

    REST是一种混合的架构风格,它的由来以及它的架构元素在笔者的前一篇文章<REST 架构风格的由来 & 元素>中已经描述了.本篇主要描述一下J2EE对REST的支持. Java是在 ...

  8. python实例:自动保存百度盘资源到百度盘中

    本实例的实现逻辑是,应用selenium UI自动化登录百度盘,读取存储百度分享地址和提取码的txt文档,打开百度盘分享地址,填入提取码,然后保存到指定的目录中 全部代码如下: # -*-coding ...

  9. hibernate.validator 与 jackson

    1.使用hibernate.validator校验非空,在FormData类中 name字段上面加@NotEmpty @NotEmpty(message = "姓名必填") pri ...

  10. 解决ionic 中 $ionicHistory.goBack()无法返回

    这种解决方法目前只适合用了 <ion-side-menus>这一组件的<ion-view> 解决方法: 在<ion-view>下一级中包一个div,如下图: 搞了几 ...