zoukankan      html  css  js  c++  java
  • Java并发-volatile的原理及用法

    Java并发-volatile的原理及用法


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

    1 int i=0;
    2 //线程一中执行
    3 i=1;
    4 //线程二中执行
    5 int j=0;
    6 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保证有序性
    有序性,及程序按照编写的顺序先后执行。
      举个例子、例二:

    1 int i=0;//1
    2 int j=1;//2
    3 i++;//3
    4 j++;//4

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

    1 int i=0;//1
    2 int j=1;//2
    3 i++;//3
    4 j=i+1;//4

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

    1 User user;//1
    2 boolean flag=flase;//2
    3 //线程一
    4 user=new User();//3
    5 flag=true;//4
    6 //线程二
    7 if(flag){
    8 user.name="张振力";//5
    9 }

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

    1 volatile boolean flag =false;
    2 int i,j,x,y;
    3 i=10;//1
    4 j=10;//2
    5 flag=true;//3
    6 x=10;//4
    7 y=10;//5

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

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

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

    1.可见性

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

    2.有序性

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

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

  • 相关阅读:
    Windows系统中监控文件复制操作的几种方式
    右击菜单一键优化(增加新建office2003、新建reg和bat,删除新建公文包、新建wps、新建rar)
    美颜我迪!
    为什么我们不要 .NET 程序员
    访问局域网电脑提示“指定的网络名不存在”的解决办法
    WIN7X64SP1极限精简版by双心
    一键精简Windows不常用的字体.cmd
    dll文件32位64位检测工具以及Windows文件夹SysWow64的坑【转发】
    Win7精简成功后的总结
    dependency walker检查dll依赖关系目录设置的问题
  • 原文地址:https://www.cnblogs.com/java-zzl/p/9486903.html
Copyright © 2011-2022 走看看