小编带您Volatile的详解

volatile关键字修饰的共享变量主要有两个特点:1.保证了不同线程访问的内存可见性 2.禁止重排序
在说内存可见性和有序性之前,我们有必要看一下Java的内存模型(注意和JVM内存模型的区分)
为什么要有java内存模型?
首先我们知道内存访问和CPU指令在执行速度上相差非常大,完全不是一个数量级,为了使得java在各个平台上运行的差距减少,哪些搞处理器的大佬就在CPU上加了各种高速缓存,来减少内存操作和CPU指令的执行速度差距。而Java在java层面又进行了一波抽象,java内存模型将内存分为工作内存和主存,每个线程从主存load数据到工作内存,将load的数据赋值给工作内存上的变量,然后该工作内存对应的线程进行处理,处理结果在赋值给其工作内存,然后再将数据赋值给主存中的变量(这时候需要有一张图)。
小编带您Volatile的详解
小编带您Volatile的详解
使用工作内存和主存虽然加快了处理速度,但是也带来了一些问题,比如下面这个例子
1 int i = 1;
2 i = i+1;
当在单线程情况下,i最后的值一定是2;但是在两个线程情况下一定是3吗?那就未必了。当线程A读取i的值为1,load到其工作内存,这时CPU切换至线程B,线程B读取i的值也是1,然后对加1然后save到主存,这时线程A也对i进行加1,也save回主存,但最终i的值为2。如果写操作比较慢,你读到的值还有可能是1,这就是缓存不一致的问题。JMM就是围绕着原子性,内存可见性,有序性这三个特征建立的。通过解决这个三个特征来解决缓存不一致的问题。而volatile主要针对于内存可见性和有序性。
原子性
原子性是指一个操作要么成功,那么失败,没有中间状态,比如i=1,直接读取i的值,这肯定是原子操作;但是i++,看似好像是,其实需要先读取i的值,然后+1,最后在赋值给i,需要三个步骤,这就不是原子性操作。在JDK1.5引入了boolean、long、int对应的原子性类AtomicBoolean、AtomicLong、AtomicInteger,他们可以提供原子性操作。
内存可见性
具有内存可见性的变量在被线程修改以后,会立刻刷新到主存并使其他线程的缓存行上的数据失效。
volatile修饰的变量具有内存可见性,主要表现为:当写一个volatile变量时,JMM会将该线程对应的工作内存中的共享变量立即刷新到主存;当读一个volatile变量时,JMM会把该线程对应的工作内存中的值置为无效,然后从主存中进行读取,但是如果没有线程对该共享变量进行修改,则不会触发该操作。
有序性
JMM是允许处理器和编译器对指令进行重排序的,但规定了as-if-serial,即无论怎么重排序,最终结果都是一样的。比如下面这段代码:
1 int weight = 10; //A
2 int high = 5; //B
3 int area = high weight high; //C
这段代码中可以按照A-->B-->C执行,也可以按照B-->A-->C执行,因为A和B是相互独立的,而C依赖于A、B,所以C不能排到A或B的前面。JMM保证了单线程的重排序,但是在多线程中就容易出现问题。比如下面这种情况
小编带您Volatile的详解
小编带您Volatile的详解
1 boolean flag = false;
2 int a = 0;
4 public void write(){
5 int a = 2; //1
6 flag = true; //2
7 }
8 public void multiply(){
9 if(flag){ //3
10 int ret = a * a ; //4
11 }
12 }
小编带您Volatile的详解
小编带您Volatile的详解
如果有两个线程执行上面的代码,线程1先执行write方法,随后线程2执行multiply方法。最后结果一定是4吗,不一定。
小编带您Volatile的详解
小编带您Volatile的详解
如图,JMM对1和2进行了重排序,先将flag设置为true,这是线程2执行,由于a还没有赋值,所以最后ret的值为0;
如果使用volatile关键字修饰flag,禁止重排序,可以保证程序的有序性,也可以使用synchronized或者lock这种重量级锁来保证有序性,但性能会下降。
另外,JMM具备一些先天的有序性,即不需要通过任何手段就可以保证的有序性,通常称为happens-before原则。<<JSR-133:Java Memory Model and Thread Specification>>定义了如下happens-before规则:
第1条规则程序顺序规则是说在一个线程里,所有的操作都是按顺序的,但是在JMM里其实只要执行结果一样,是允许重排序的,这边的happens-before强调的重点也是单线程执行结果的正确性,但是无法保证多线程也是如此。
第2条规则监视器规则其实也好理解,就是在加锁之前,确定这个锁之前已经被释放了,才能继续加锁。
第3条规则,就适用到所讨论的volatile,如果一个线程先去写一个变量,另外一个线程再去读,那么写入操作一定在读操作之前。
第4条规则,就是happens-before的传递性。
需要注意的是,被volatile修饰的共享变量只满足内存可见性和禁止重排序,并不能保证原子性。比如volatile i++。
小编带您Volatile的详解
小编带您Volatile的详解
1 public class Test {
2 public volatile int inc = 0;
4 public void increase() {
5 inc++;
6 }
8 public static void main(String[] args) {
9 final Test test = new Test();
10 for(int i=0;i<10;i++){
11 new Thread(){
12 public void run() {
13 for(int j=0;j<1000;j++)
14 test.increase();
15 };
16 }.start();
17 }
18
19 while(Thread.activeCount()>1) //保证前面的线程都执行完
20 Thread.yield();
21 System.out.println(test.inc);
22 }
小编带您Volatile的详解
小编带您Volatile的详解
按道理来说结果是10000,但是运行下很可能是个小于10000的值。有人可能会说volatile不是保证了可见性啊,一个线程对inc的修改,另外一个线程应该立刻看到啊!可是这里的操作inc++是个复合操作啊,包括读取inc的值,对其自增,然后再写回主存。
假设线程A,读取了inc的值为10,这时候被阻塞了,因为没有对变量进行修改,触发不了volatile规则。
线程B此时也读读inc的值,主存里inc的值依旧为10,做自增,然后立刻就被写回主存了,为11。
此时又轮到线程A执行,由于工作内存里保存的是10,所以继续做自增,再写回主存,11又被写了一遍。所以虽然两个线程执行了两次increase(),结果却只加了一次。
有人说,volatile不是会使缓存行无效的吗?但是这里线程A读取到线程B也进行操作之前,并没有修改inc值,所以线程B读取的时候,还是读的10。
又有人说,线程B将11写回主存,不会把线程A的缓存行设为无效吗?但是线程A的读取操作已经做过了啊,只有在做读取操作时,发现自己缓存行无效,才会去读主存的值,所以这里线程A只能继续做自增了。
综上所述,在这种复合操作的情景下,原子性的功能是维持不了了。但是volatile在上面那种设置flag值的例子里,由于对flag的读/写操作都是单步的,所以还是能保证原子性的。
要想保证原子性,只能借助于synchronized,Lock以及并发包下的atomic的原子操作类了,即对基本数据类型的 自增(加1操作),自减(减1操作)、以及加法操作(加一个数),减法操作(减一个数)进行了封装,保证这些操作是原子性操作。
volatile底层原理
如果将使用volatile修饰的代码和未使用volatile修饰的代码都编译成汇编语言,会发现,使用volatile修饰的代码会多出一个lock前缀指令。
lock前缀指令相当于一个内存屏障,内存屏障的作用有以下三点:
①重排序时,不能把内存屏障后面的指令排序到内存屏障前
②使得本CPU的cache写入内存
③写入动作会引起其他CPU缓存或内核的数据无效,相当于修改对其他线程可见。
volatile的应用场景
因为volatile对复合操作无效,所以volatile修饰像上面例子中的flag这样的只会发生读/写的标记型字段。
在单利模式中,volatile还可以修饰成员变量,防止初始化时的指令重排序。
小编带您Volatile的详解
小编带您Volatile的详解
1 class Singleton{
2 private volatile static Singleton instance= null;
4 private Singleton(){
6 }
8 public static Singleton getInstance(){
9 if(instance==null){
10 synchronized(Singleton.class){
11 if(instance==null){
12 instance = new Singleton();
13 }
14 }
15 }
16 return instance;
17 }
18 } 小编每天会定期更新论文及视频,希望大家多多关注与支持 每天晚上20:00会在腾讯课堂上分享免费往期干货QQ:561487941

原文地址:http://blog.51cto.com/13842645/2165540

时间: 2024-10-09 10:03:11

小编带您Volatile的详解的相关文章

AME_Thoy_Oracle自带AME审批链详解AME Standard Handler

Oracle 自带了3大类,13个子类的审批链Action Type, 对应了13个标准的AME Standard Handler 1. 按主管层次审批 absolute job level / chains of authority based on absolute job levelfinal approver only / chains of authority containing only the final job-level approvermanager then final

AME_Oracle自带AME审批链详解AME Standard Handler(概念)

Oracle 自带了3大类,13个子类的审批链Action Type, 对应了13个标准的AME Standard Handler 1. 按主管层次审批 absolute job level / chains of authority based on absolute job levelfinal approver only / chains of authority containing only the final job-level approvermanager then final

小编带您进入SpringBoot (1) idea下的环境搭建及demo

1.Spring Boot简介wiki上的介绍: Spring Boot是Spring的常规配置解决方案,用于创建可以"运行"的独立的,生产级的基于Spring的应用程序.[22]它预先配置了Spring对Spring平台和第三方库的最佳配置和使用的"见解视图",因此您可以尽量少开始.大多数Spring Boot应用程序只需要很少的Spring配置.特征: 创建独立的Spring应用程序直接嵌入Tomcat或Jetty(无需部署WAR文件)提供自以为是的"

小编带你了解Netty 系列七(那些开箱即用的 ChannelHandler).

一.前言Netty 为许多通用协议提供了编×××和处理器,几乎可以开箱即用, 这减少了你在那些相当繁琐的事务上本来会花费的时间与精力.另外,这篇文章中,就不涉及 Netty 对 WebSocket协议 的支持了,因为涉及的篇幅有点大,会在下一篇文章做一个具体的介绍. 回到顶部二.SSL 协议SSL 协议是安全协议,层叠在其他协议之上.为了支持 SSL/TLS, Java 提供了 javax.net.ssl 包,它的 SSLContext 和 SSLEngine 类使得实现解密和加密相当简单直接.

没有基础小编带你,用python画机器猫(有代码)

小编带你玩python 没有基础小编带你,用python画机器猫.只需要python3和小编的代码即可.python3小编送,代码文章有,现在就差个你了. 运行不了的找小编,小编包教会你. 重要的事情说三遍: python3小编送,代码文章有. python3小编送,代码文章有. python3小编送,代码文章有. 运行不了的找小编,加Q君羊 八八三四四四一零六. 君羊里的小伙伴和管理员的会这个运行这个源代码.需要学习视频的直接找管理员要,就说是小编让的,烦死她.欢迎小伙伴的加入. 原文地址:h

Ubuntu11.10 带图形安装步骤详解

成功!  关于Ubuntu的网络设置.静态IP设置,见 CenTOS6.5的静态IP设置:见  http://www.cnblogs.com/zlslch/p/5065577.html CentOS6.5(带图形安装)在使用过程中遇到的一些网络问题迷惑:见 http://www.cnblogs.com/zlslch/p/5826369.html Centos 6.5下的OPENJDK卸载和SUN的JDK安装.环境变量配置:见 http://www.cnblogs.com/zlslch/p/582

微领地小蜜app模式系统开发详解

如今企业竞争的最高境界, 不再是产品的竞争.人才的竞争.营销的竞争-- 而是一种商业模式(即赢利模式)的竞争. 微领地小蜜软件,微领地小蜜系统,小蜜APP,微领地小蜜平台,微领地小蜜开发公司,微领地小蜜服务模式,微领地小蜜商圈.O2O.C2B.B2C.F2C.B2B.C2N.C2C等微商城.App.手机网页.PC商城.小程序,微商管理系统.一物一码.防伪防窜.扫码红包.质量追溯.分销.全返.分红.拼团.互联网+直播+定制服务·系统开发提供中心. 回首过去,创新与发展裹带着机遇和挑战,微领地小蜜在

Javascript异步编程之setTimeout与setInterval详解分析(一)

Javascript异步编程之setTimeout与setInterval 在谈到异步编程时,本人最主要会从以下三个方面来总结异步编程( 注意: 特别解释:是总结,本人也是菜鸟,所以总结不好的,请各位大牛多多原谅!) 1. setTimeout与setInterval详细分析基本原理. 接下来这篇博客会总结setTimeout和setInterval基本点,对于上面三点会分三篇博客分别来总结,对于知道上面三点的人,但是又不是非常了解全面知识点的码农来说,没有关系的,我们可以慢慢来学习,来理解,或

多线程之 Volatile 变量 详解

Java 理论与实践: 正确使用 Volatile 变量 原文:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html 总结: 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility).互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据.可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另