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,线程二并没有及时得到线程一修改的值,这就是不可见性;
  如果声明i变量是 volatile 的(volatile int 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中并一定不会按照这个顺序执行,因为处理器为了提高运行效率,它会改变各个语句的执行顺序,但是不改变运行结果,就是改变后的顺序运行结果和改变前的结果一样,就行如果按照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 int i=0;//1
2 boolean flag=flase;//2
3 //线程一
4 i=10;//3
5 flag=true;//4
6 //线程二
7 if(flag){
8   i++;//5
9 }

  上面这个程序的意思就是,把10赋给i时,flag设置为真,在线程二中如果flag为真就为i的值加1;但是在实际中因为3和4并没有依赖,会被重排序,在线程二中得到i值可能还会是0,这就和预想的不一样。
  通过定义volatile就可以保证一定的有序性,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,那肯定能保证i=10一定执行过来。

1 int i=0;//1
2 volatile boolean flag=flase;//2
3 //线程一
4 i=10;//3
5 flag=true;//4
6 //线程二
7 if(flag){
8 i++;//5
9 }

 后续会在我的设计模式里面写一下volatile怎样确保单例模式在多线程下的数据的正确。

猜你喜欢

转载自www.cnblogs.com/java-zzl/p/9486903.html
今日推荐