在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序。

1. 内存可见性

Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程只能访问自己的工作内存,不可以访问其它线程的工作内存。工作内存中保存了主内存中共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中,其JVM内存模型大致如下图。

而JAVA内存模型规定工作内存与主内存之间的交互协议,其中包括8种原子操作:

1) lock:将主内存中的变量锁定,为一个线程所独占 
2) unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量 
3) read:将主内存中的变量值读到工作内存当中 
4) load:将read读取的值保存到工作内存中的变量副本中。 
5) use:将值传递给线程的代码执行引擎 
6) assign:将执行引擎处理返回的值重新赋值给变量副本 
7) store:将变量副本的值存储到主内存中。 
8) write:将store存储的值写入到主内存的共享变量当中。

其中lock和unlock定义了一个线程访问一次共享内存的界限,而其它操作下线程的工作内存与主内存的交互大致如下图所示。

从上图可以看出,read and load 主要是将主内存中数据复制到工作内存中,use and assign则主要是使用数据,并将改变后的值写入到工作内存,store and write则是用工作内存数据刷新主存相关内容。

但是以上的一系列操作并不是原子的,也既是说在read and load之后,主内存中变量的值发生了改变,这时再use and assign则并不是取的最新的值,而我认为的内存可见性可粗略描述为,如果数据A在一个线程中的改变能够立即被其他线程可见,那么则说数据A具有内存可见性,也既是说如果数据A具有内存可见性,那么即使一个线程在read and load之后,数据A的值被改变了,在use and assign时也能获取到数据A最新的值并使用,那么该如何保证线程在每次use and assign时都是获取的数据A的最新的值呢?

其实只要线程在每次use and assign时都是直接从主内存中获取数据A的值,就能够保证每次use and assign都是获取的数据A的最新的值,也既是能保证数据A的内存可见性,而volatile关键字的作用之一便是系统每次用到被它修饰过的变量时都是直接从主内存当中提取,而不是从Cache中提取,同时对于该变量的更改会马上刷新回主存,以使得各个线程取出的值相同,这里的Cache可以理解为线程的工作内存。当然了volatile关键字还有另外一个非常重要的作用,即局部阻止指令重排序。

(注:synchronized或其它加锁,也能保证内存可见性,但实现方式略有不同,也不在本文的讨论范围内)

2. 指令重排序

首先看下以下线程A和线程B的部分代码:

线程A:
content = initContent(); //(1)
isInit = true; //(2)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
线程B
while (isInit) { //(3)
content.operation(); //(4)
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

从常规的理解来看,上面的代码是不会出问题的,但是JVM可以对它们在不改变数据依赖关系的情况下进行任意排序以提高程序性能(遵循as-if-serial语义,即不管怎么重排序,单线程程序的执行结果不能被改变),而这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不会被编译器和处理器考虑,也即是说对于线程A,代码(1)和代码(2)是不存在数据依赖性的,尽管代码(3)依赖于代码(2)的结果,但是由于代码(2)和代码(3)处于不同的线程之间,所以JVM可以不考虑线程B而对线程A中的代码(1)和代码(2)进行重排序,那么假设线程A中被重排序为如下顺序:

线程A:
isInit = true; //(2)
content = initContent(); //(1)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

对于线程B,则可能在执行代码(4)时,content并没有被初始化,而造成程序错误。那么应该如何保证绝对的代码(2) happens-before 代码(3)呢?没错,仍然可以使用volatile关键字。

volatile关键字除了之前提到的保证变量的内存可见性之外,另外一个重要的作用便是局部阻止重排序的发生,即保证被volatile关键字修饰的变量编译后的顺序与 也即是说如果对isInit使用了volatile关键字修饰,那么在线程A中,就能保证绝对的代码(1) happens-before 代码(2),也便不会出现因为重排序而可能造成的异常。

3. 总结

综上所诉,volatile关键字最主要的作用是: 
1) 保证变量的内存可见性 
2) 局部阻止重排序的发生

4. 附录 - happens-before原则

英文原文:

  • Each action in a thread happens before everyaction in that thread that comes later in the program’s order.
  • An unlock on a monitor happens before everysubsequent lock on that same monitor.
  • A write to a volatile field happens before everysubsequent read of that same volatile.
  • A call to start() on a thread happens before anyactions in the started thread.
  • All actions in a thread happen before any otherthread successfully returns from a join() on that thread.

中文描述:

  • 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。 (并没有)
  • 监视器锁规则:对一个监视器锁的解锁,happens-before 于随后对这个监视器锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before 于任意后续对这个volatile域的读。
  • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
  • Thread.start()的调用会happens-before于启动线程里面的动作。
  • Thread中的所有动作都happens-before于其他线程从Thread.join中成功返回。

其中第一条程序顺序规则并不合理,因为在线程中只有存在数据依赖性才不会被重排序,而没有任何数据依赖性的操作,依然可能被编译器重排序。

5. 参考文献

[1] Brian Goetz.Java并发编程实战.机械工业出版社.2012 
[2] http://ifeve.com/easy-happens-before/ 
[3] http://www.infoq.com/cn/articles/java-memory-model-2/ 
[4] http://www.cnblogs.com/mengyan/archive/2012/08/22/2651575.html 
[5] http://my.oschina.net/chihz/blog/58035 
[6] http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 
[7] http://ifeve.com/jvm-reordering/ 
[8] ……

以上仅为个人学习所记笔记,如有错误,欢迎指正

参考链接:http://blog.csdn.net/t894690230/article/details/50588129

http://blog.csdn.net/uniquewonderq/article/details/48113071

volotile关键字的内存可见性及重排序的更多相关文章

  1. 原子性、内存可见性和重排序——重新认识synchronized和volatile

    一.原子性 原子性操作指相应的操作是单一不可分割的操作.例如,对int变量count执行count++d操作就不是原子性操作.因为count++实际上可以分解为3个操作:(1)读取变量count的当前 ...

  2. Java内存模型(三)原子性、内存可见性、重排序、顺序一致性、volatile、锁、final

          一.原子性 原子性操作指相应的操作是单一不可分割的操作.例如,对int变量count执行count++d操作就不是原子性操作.因为count++实际上可以分解为3个操作:(1)读取变量co ...

  3. Java内存模型_重排序

    重排序:是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段 1..编译器优化的重排序.编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序. 2..指令级并行的重排序.现 ...

  4. volatile关键字与内存可见性

    前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开 ...

  5. 详解volatile 关键字与内存可见性

    先来看一个例子: public class VolatileTest {            public static void main(String[] args) {           T ...

  6. 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before

    第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...

  7. volatile关键字及内存可见性

    先看一段代码: package com.java.juc; public class TestVolatile { public static void main(String[] args) { T ...

  8. 【JUC系列第一篇】-Volatile关键字及内存可见性

    作者:毕来生 微信:878799579 什么是JUC? JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类 2.Volatile关键字 1.如果一个变量被volat ...

  9. volatile关键字与内存可见性&原子变量与CAS算法

    1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...

随机推荐

  1. SharePoint 会话(Session)状态和状态服务

    1. 启用会话状态 Enable-SPSessionStateService –DefaultProvision 或 Enable-SPSessionStateService –DatabaseNam ...

  2. 写在连载之前——DIY微型操作系统篇

    这个博客开了这么久都没写过什么东西.可能是因为我想写的东西在网上都能找得到,所以自己也懒得去写了. 但是这次当我在看<30天自制操作系统>这本书的时候发现,如果不用作者原版的光盘软件,要自 ...

  3. 中国教授在BlackHat现场演示破解SIM卡AES-128加密

    使用一个PC和示波器克隆3G/4G SIM卡,破解过程只需十分钟.上海交大教授郁昱现场展示了如何成功复制SIM卡,以及一张克隆卡如何变更了支付宝的密码并潜在盗取账户资金. 破解SIM卡加密 今年二月, ...

  4. PAT 列车厢调度   (25分)(栈和容器的简单应用)

    1 ====== <--移动方向 / 3 ===== \ 2 ====== -->移动方向 大家或许在某些数据结构教材上见到过“列车厢调度问题”(当然没见过也不要紧).今天,我们就来实际操 ...

  5. Spring4.3整合Hibernate4.3搭建Spring MVC

    1,web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi=&qu ...

  6. 今天遇到的一个奇葩的NoClassFound的问题

    nohup的日志中报错 java.lang.NoClassDefFoundError: org/apache/catalina/core/ApplicationContext$DispatchData ...

  7. (转)Linux 定时关机、休眠命令

    立刻关机:sudo haltsudo init 0 sudo shutdown -h nowsudo shutdown -h 0....定时/延时关机:sudo shutdown -h 19:3019 ...

  8. 【网络通讯】Nat知识了解

    一.Nat的含义 NAT(Network Address Translation,网络地址转换)是1994年提出的.当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址 ...

  9. dockercompose up build fail

    docker https://auth.docker.io/token dial tcp lookup auth.docker.io i/o timeo Error response from dae ...

  10. ActiveMQ默认协议和IO模型优化

    在ActiveMQ的官方网站上,列出了目前ActiveMQ中支持的所有消息协议,它们是:AMQP.MQTT.OpenWire.REST.Stomp.XMPP: 不同的协议需要设置不同的网络监听端口,这 ...