Java并发-volatile的原理及用法


volatile属性:可见性、保证有序性、不保证原子性。
一、volatile可见性
  在Java的内存中所有的变量都存在主内存中,每个线程有单独CPU缓存内存,多个线程对同一个变量读取时,会从主内存中把变量拷贝到自己的CPU缓存中,线程之间也无法直接访问对方CPU缓存内存中的变量,只能通过主内存传递变量的值;
  举个例子、例一;

 int i=0;
//线程一中执行
i=1;
//线程二中执行
int j=0;
j=i;

  上面这个程序在线程一中,读取内存变量i的值到线程一的CPU缓存中,线程一的CPU缓存中并将i的值设为1,这时还没来的及将i的值从线程一的CPU缓存中写入到主内存中时,此时线程二从主内存中读取到i的值到线程二的CPU缓存中,此时i还是0,线程二并没有及时得到线程一修改的值,这就是不可见性;
  如果把上面程序的int i=0换成volatile int i=0,就可以保证线程二可以立刻可见线程一修改i后的值,因为JVM 保证了每次读变量都从内存中读,相当于跳过 CPU cache 这一步,让线程一直接修改主内存中i的值;
  但是如果将i换成volatile int i,将i=1换成i++,线程二对线程一的改变i后的值仍是不可见的,原因就是下面要说的原子性;
二、volatile不可保证原子性
  原子性就是指操作不可分割,比如i=1这个就不可分割,i++就包含三个操作:读取i的值,进行加1操作,写入新的值,这就可分割不是原子操作。如果将i换成volatile int i,将i=1换成i++,当线程一执行到对i加1操作时,还没有来得及写入新值时,线程二执行j=i,线程获取的i值任然为0;可以通过synchronized和Lock来实现更大范围操作的原子性。
三、volatile保证有序性
有序性,及程序按照编写的顺序先后执行。
  举个例子、例二:

 int i=0;//
int j=1;//
i++;//
j++;//

  上面这个程序按照正常逻辑执行顺序应该是1>2>3>4;但是在JVM中并一定不会按照这个顺序执行,因为处理器为了提高运行效率,在JVM中的及时编译存在指令重排序的优化,它会改变各个语句的执行顺序,但是不改变运行结果,就是改变后的顺序运行结果和改变前的结果一样,就行如果按照1>3>2>4,虽然改变顺序,但并不影响结果;
如果把程序改变成这个样子、例三:

 int i=0;//
int j=1;//
i++;//
j=i+1;//

  上面这个程序执行顺序一定是1、2在3前面,3一定会在4前面,1和2顺序可变,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令指令2必须用到指令1的结果,那么处理器会保证指令1会在指令2之前执行。
重排序虽然对单线程没影响但是对多线程就会产生影响,使结果不一样;
举个例子、例四:

 User user;//
boolean flag=flase;//2
//线程一
user=new User();//
flag=true;//4
//线程二
if(flag){
user.name="张振力";//
}

  上面这个程序的意思就是,如果在线程一user完成初始化,就把flag设置为true,在线程二中如果flag为true,就初始化user.name的值;但是在实际中因为3和4并没有依赖,会被重排序,语句4先执行,语句3还没执行,在线程二中得到user值就会为空,这时候给name赋值就会报错。
  通过定义volatile就可以保证语句3一定在语句4前执行,就是因为volatile定义的变量在赋值时,在其上面的程序是一定完成过的,在其后的程序一定是没完成的,而且其前完成的操作对其后的是可见的;举个例子、例五:

 volatile boolean flag =false;
int i,j,x,y;
i=10;//
j=10;//
flag=true;//
x=10;//
y=10;//

  上面的例子执行的顺序就是在3之前1和2语句已经执行完(不保证1、2的顺序),但4和5语句(不保证4、5的顺序)肯定还没执行;
  再返回例四里面看,如果flag定义为volatile,那肯定能保证语句3一定在语句4前执行,如果flag为true,那user肯定完成了初始化,就不会报错。

 User user;//
volatile boolean flag=flase;//2
//线程一
user=new User();//
flag=true;//4
//线程二
if(flag){
user.name="张振力";//
}

四、volatile实现可见性和有序性的原理: 

1.可见性

  如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。 这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。

2.有序性

  但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查 自己的缓存是否过期,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。 这一步确保了其他线程获得的声明了volatile变量都是从主内存中获取最新的。

  在这里就讲这一个使用场景:状态标记,后续会再写另一个使用场景,单例模式下的volatile确保单例对象的返回的正确性。

Java并发-volatile的原理及用法的更多相关文章

  1. Java并发——volatile的原理

    111 Java并发——volatile的原理

  2. java 并发——volatile

    java 并发--volatile 介绍 维基百科: volatile 是一个类型修饰符(type specifier).volatile 的作用是确保本条指令不会因编译器的优化而省略,且要求每次直接 ...

  3. java并发基础及原理

    java并发基础知识导图   一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...

  4. java并发 - 自底向上的原理分析

    [TOC] 事先声明,我只是java并发的新手,这篇文章也只是我阅读<java并发编程的艺术>一书(内容主要涉及前3章)的一些总结和感悟.希望大家能多多讨论,对于错误的地方还请指出. 0. ...

  5. Java并发--volatile详情

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

  6. JAVA并发--volatile

    学过计算机组成原理的一定知道,为了解决内存速度跟不上CPU速度这个问题,在CPU的设计中加入了缓存机制,缓存的速度介于CPU和主存之间.在进行运算的时候,CPU将需要的数据映射一份在缓存中,然后直接操 ...

  7. Java 并发编程-不懂原理多吃亏(送书福利)

    作者 | 加多 关注阿里巴巴云原生公众号,后台回复关键字"并发",即可参与送书抽奖!** 导读:并发编程与 Java 中其他知识点相比较而言学习门槛较高,从而导致很多人望而却步.但 ...

  8. java并发编程系列原理篇--JDK中的通信工具类Semaphore

    前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...

  9. Java并发--volatile关键字

    一.volatile的实现原理 synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁,而volatile就可以说是JVM提供的最轻量级的同步机制.JMM告诉我们,各个线程会将共 ...

随机推荐

  1. .NETMVC小笔记

    .NETMVC如何不引用_Layout.chtml view文件夹里面有个_ViewStart.cshtml文件,打开可以看到 @{ Layout = "~/Views/Shared/_La ...

  2. 慕容小匹夫 Unity3D移动平台动态读取外部文件全解析

    Unity3D移动平台动态读取外部文件全解析   c#语言规范 阅读目录 前言: 假如我想在editor里动态读取文件 移动平台的资源路径问题 移动平台读取外部文件的方法 补充: 回到目录 前言: 一 ...

  3. 学Android开发的人可以去的几个网站

    学Android开发的人可以去的几个网站 1.<IT蓝豹>Android开源项目分享平台国内非常好的一个Android开发者分享站,分享android所有特效,每天都有最新的Android ...

  4. centos 卸载和安装软件

    rpm -qa 列出所有已安装软件包 rpm -e packagename  删除软件包 rpm -e --nodeps packagename  强制删除软件和依赖包 rpm -q 包名     查 ...

  5. InvalidArgumentError (see above for traceback): Assign requires shapes of both tensors to match. lhs shape= [2048,38] rhs shape= [2048,2]

    做tensorflow object detection 中,清空下checkpoint就可以啦

  6. 线程执行synchronized同步代码块时再次重入该锁过程中抛异常,是否会释放锁

    一个线程执行synchronized同步代码时,再次重入该锁过程中,如果抛出异常,会释放锁吗? 如果锁的计数器为1,抛出异常,会直接释放锁: 那如果锁的计数器为2,抛出异常,会直接释放锁吗? 来简单测 ...

  7. JS获取任意月份的最后一天

    在获取月份天数的时候,因为月份不同,所以每个月的天数也有差异,并且由于平闰年,2月份天数也有所不同,导致程序中获取任意月份的天数十分复杂,下面就用这个方法解决此问题,调用此方法将任意年份和月份传进去, ...

  8. php挖掘数据编码问题

    if(json_encode($jkkey) == 'null'){//判断不是utf8会返回空 $jkkey=mb_convert_encoding($jkkey,'utf-8','gbk'); } ...

  9. 2018面向对象程序设计(Java)第11周学习指导及要求

    2018面向对象程序设计(Java)第11周学习指导及要求 (2018.11.8-2018.11.11)   学习目标 (1) 掌握Vetor.Stack.Hashtable三个类的用途及常用API: ...

  10. 第三章,DNA序列的进化演变

    31.前言 3.1.两个序列间的核苷酸差异 来自同一祖先序列的两条后裔序列,之间的核苷酸的差异随着时间的增加而变大.简单的计量方法,p距离 3.2.核苷酸代替数的估计 3.3.Jukes和Cantor ...