volotile关键字的内存可见性及重排序
在理解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关键字的内存可见性及重排序的更多相关文章
- 原子性、内存可见性和重排序——重新认识synchronized和volatile
一.原子性 原子性操作指相应的操作是单一不可分割的操作.例如,对int变量count执行count++d操作就不是原子性操作.因为count++实际上可以分解为3个操作:(1)读取变量count的当前 ...
- Java内存模型(三)原子性、内存可见性、重排序、顺序一致性、volatile、锁、final
一.原子性 原子性操作指相应的操作是单一不可分割的操作.例如,对int变量count执行count++d操作就不是原子性操作.因为count++实际上可以分解为3个操作:(1)读取变量co ...
- Java内存模型_重排序
重排序:是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段 1..编译器优化的重排序.编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序. 2..指令级并行的重排序.现 ...
- volatile关键字与内存可见性
前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开 ...
- 详解volatile 关键字与内存可见性
先来看一个例子: public class VolatileTest { public static void main(String[] args) { T ...
- 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before
第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...
- volatile关键字及内存可见性
先看一段代码: package com.java.juc; public class TestVolatile { public static void main(String[] args) { T ...
- 【JUC系列第一篇】-Volatile关键字及内存可见性
作者:毕来生 微信:878799579 什么是JUC? JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类 2.Volatile关键字 1.如果一个变量被volat ...
- volatile关键字与内存可见性&原子变量与CAS算法
1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...
随机推荐
- C# 历史曲线控件 基于时间的曲线控件 可交互的高级曲线控件 HslControls曲线控件使用教程
本篇博客主要对 HslControls 中的曲线控件做一个详细的教程说明,大家可以根据下面的教程开发出高质量的曲线控件 Prepare 先从nuget下载到组件,然后就可以使用组件里的各种组件信息了. ...
- Spring学习(二)--IOC
一.什么是IOC? 孤傲苍狼总结的理解: https://www.cnblogs.com/xdp-gacl/p/4249939.html 我的理解(不知道对不对哈,不对的话请各位大神指出): IOC往 ...
- MySQL性能优化方法三:索引优化
原文链接:http://isky000.com/database/mysql-performance-tuning-index 大家都知道索引对于数据访问的性能有非常关键的作用,都知道索引可以提高数据 ...
- git中的needs merge问题
这个问题是在先“储藏”起来了,后面再调用出来出现的错误. 解决的方法就是通过git add ,git commit -m 提交上去就可以了.
- HDU 5178:pairs(二分,lower_bound和upper_bound)
pairs Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Subm ...
- 【问题】C4D中设置了界面颜色,如何恢复默认?
由于C4D没有恢复默认设置的选项,恢复默认的时候比较麻烦,这里简单删除一下配置文件就好了. 1.打开C4D设置,点击下面的[打开配置文件夹],并关掉C4D. (即C:\Users\你的用户名\AppD ...
- JS校验 if (! temp_var) {} //拦截 ''和 undefined
if (! aaa) {} //拦截 ' ' 和 undefined 和 0 不拦截null
- JQuery禁止回车提交表单
//禁止回车键提交表单——动态绑定 $(function(){ $("input").on('keypress', //所有input标签回车无效,当然,可以根据需求自定义 fu ...
- aircrack-ng 工具集学习
一.aircrack-ng简介 aircrack-ng是Aircrack项目的一个分支.是一个与802.11标准的无线网络分析有关的安全软件,主要功能有:网络侦测,数据包嗅探,WEP和WPA/WPA2 ...
- TensorFlow笔记-08-过拟合,正则化,matplotlib 区分红蓝点
TensorFlow笔记-08-过拟合,正则化,matplotlib 区分红蓝点 首先提醒一下,第7讲的最后滑动平均的代码已经更新了,代码要比理论重要 今天是过拟合,和正则化,本篇后面可能或更有兴趣, ...