在理解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. vee-validate 中文配置报错及自定义规则 报错.updateDictionary/.addlocale is not a function

    原因由于vee-validate版本更新的问题导致的. 1. 原因分析: a 新版vee-validate的语言配置方法变化了, Validator.localize('zh_CN', zh_CN) ...

  2. pat 集合相似度

    L2-005. 集合相似度 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定两个整数集合,它们的相似度定义为:Nc/Nt*1 ...

  3. oracle 统计sql

    最近在研究项目时发现如下sql, select 3 agentOfGCount, 0 workingCount, 0 restingCount, 0 busyingCount, 0 connectin ...

  4. iTween的用法总结

    Unity3D插件-iTween的基本用法 本文提供全流程,中文翻译.Chinar坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) 1 Introd ...

  5. BZOJ4767: 两双手【组合数学+容斥原理】

    Description 老W是个棋艺高超的棋手,他最喜欢的棋子是马,更具体地,他更加喜欢马所行走的方式.老W下棋时觉得无聊,便决定加强马所行走的方式,更具体地,他有两双手,其中一双手能让马从(u,v) ...

  6. elasticsearch问题解决之分片副本UNASSIGNED

    在上一篇文章中,我记录了在windows下同一台机器上搭建es集群的步骤,第二天在向集群中创建索引的时候,出现了分片副本未分配的情况(UNASSIGNED). 虽然并不影响数据的插入和查询,但是有问题 ...

  7. MacOS 快捷键技巧

    Ctrl+↑打开mission ctrl 可以在mission ctrl 里新建桌面 ,通过Ctrl+←→切换桌面 可以拖拽应用窗口到新的桌面 Ctrl+↓可以打开fonder的所有窗口 F11 全屏

  8. 【BZOJ3295】【CQOI2011】动态逆序对

    cdq分治经典例题,然而智商掉线傻逼错误坑了两天 原题: 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你 ...

  9. $.each()与$(selector).each()

    $.each()与$(selector).each()不同, 后者专用于jquery对象的遍历, 前者可用于遍历任何的集合(无论是数组或对象),如果是数组,回调函数每次传入数组的索引和对应的值(值亦可 ...

  10. cmd下查看当前登陆用户

    cmd下查看当前登陆用户 终端下,自然可以用quser这个命令了.但是在其它如专业版2k下如何查看在线用户呢? C:\Documents and Settings\Administrator>n ...