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 包下提供常用的原子变量 ...
随机推荐
- css 和 UI 框架 ---------- vue 待续
vue 框架组件集合 radon-UI: 他人评价 -- 一个帮助你快速开发产品的Vue组件库,简洁好用,效率高,让你摆脱各种定制化的烦恼. 个人感觉 -- 组件简洁, 功能全面, 文档清晰 ...
- ACCESS删除datagridview和数据库中的一条数据,同时更新显示的方法源码
//删除,行删除 private void 删除_Click(object sender, EventArgs e) { int dgrcount = dataGridView1.SelectedRo ...
- 步步入佳境---UI入门(1)--项目建立与实现
一,本文讲解建立一个空项目,怎么一步一步的创建程序,总体的感觉一下程序流程 1,首先建立一个项目,如下:single view project,我们首先删除CHAppDelegate文件和Main. ...
- 如何在win10(64位系统)上安装apache服务器
今天装了Apache服务器,下面是我总结的方法: 一,准备软件 1.64位的apache版本 传送门:http://www.apachelounge.com/download/ 2.VC11运行库 下 ...
- PDB调试模块
这里主要是一些对于调试常用的命令:1.直接通过命令端输入进行调试 以pdb调试模式运行(主要用这个) python3 -m pdb file.py 2.在代码中导入pdb模块 import pdb 功 ...
- 代理模式及Spring AOP (一)
一.代理模式 在不更改源码的前提下,加入新功能,通常需要用到代理设计模式. 代理设计模式分类: 静态代理 动态代理 jdk动态代理 cglib动态代理 其中spring AOP的底层用的是动态代理.其 ...
- Mac无法上网
今天mac突然无法上网了, 家里的大部分设备, 都出现了重启后无法上网的问题, 猜测可能是dns有问题了. 于是乎, 在mac中添加了如下DNS 114.114.114.114 8.8.8.8 1.1 ...
- 杭电OJ1789、南阳OJ236(贪心法)解题报告
杭电OJ1789http://acm.hdu.edu.cn/showproblem.php?pid=1789 南阳OJ236http://59.69.128.203/JudgeOnline/probl ...
- 《DSP using MATLAB》Problem 3.10
用到了三角窗脉冲序列,各小题的DTFT就不写公式了,直接画图(这里只贴长度M=10的情况). 1. 代码: %% ------------------------------------------- ...
- jQuery prop() 方法
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...