Java Volatile 变量的可见性

来源:互联网 发布:nginx防止cc攻击配置 编辑:程序博客网 时间:2024/06/05 06:48

文章:点击打开链接

lock有两种: 一种是共享资源互斥,一种是可见性(visibility)        

1》互斥:是由syn的对象锁和wait(),notify()合作,lock锁来保证

              visibility:就是由volatile变量+原子操作----代替syn对象锁(主要由变量之间不能改变顺序来决定)。

      visibility:1》一个线程改变一个值后,另一个线程可以马上可以看见新改值。

                   2》happens-before:一定保证指令的原有顺序,不要打乱。

                       一个线程保存volatile变量之前,必须可以看见排在它前面变量值:表示volatile变量之前的语句不能打乱顺序,然后volatile变量存入内存。

                       一个线程读volatile变量后,得出新值,哪么排在它前面的变量的值也可以看见:表示保存时volatile变量同时保存volatile变量前的变量,然后读可以读到新值。

一、volatile 解决cpu缓存和内存数据不相同,从主存读,写到主存。

      线程操作:先写后读顺序

1、两个线程运行在不同cpu中

       1>thread1 运行在cpu1中,读时从cpu1 cache 读,有就取出,没有去主存读,并且复制一份到缓存中。

                          写时写入到cpu1 cache 中,再写入到主存,但是什么时候写入到主存,不知道。

       2>thread2 运行在cpu2中,读时从cpu2 cache 读,有就取出,没有去主存读,并且复制一份到缓存中。

                      写时写入到cpu2 cache 中,再写入到主存 。

       

2、一个线程写多个线程读,共享变量public int counter=0.

        第一次初始化时,内存默认保存counter=0。

      线程1改了数据counter=7,放入cpu1 cache中,但是并没有存入内存。

      线程2首先到cpu2 cache中读,没有就去主存读,取得counter=0,并复制到cpu2 cache中。

    **结果线程1的cpu1 cache counter=7,主存 counter=0 数据不一致。

         这样两个线程:线程1已经改变了值,前面的人明明改变共享数据值,线程2却得不到最新的值,问题了出来了?

   

  解决:public volatile int counter=0,这时 threa1 写到 cpu1 cache  后,马上写到主存

             读时 cpu2 cache无效,直接从主存读。

二、两个线程去内存取数据时,进行加1,哪么线程1和线程2的cache全部是1.

                  出错了,正确应该是tread2在thread1写入内存之后去内存取数,得出2正确值。

理由: 因为你是用volatile变量去维持原子操作,即无锁操作,所以两个线程可以同时执行,交叉执行,这样问题来了。

      时间上,thread1直接从主存读数据(jvm禁止读时从缓冲读,但是取数后放哪里不管)couter=0放入缓冲区,thread1数据还没有保存到内存,thread2取了一个数据counter=0的值,也进行改,所以thred2取数出先问题,所以只能用syn对象锁,规定操作时单一执行。

         


三、 volatile 变量一大特点:就是可以把它前面的所有non-volatile变量一起保存。

      指令排序问题,即在volatile 变量前的变量不能移动volatile 变量后面

        有的说编译器,有的说jvm会打乱指令的顺序,也就是重排,不知道到底是谁啊,管它的?

       happens-before:一条指令的执行一定在另一条指令之前执行,也就是保证顺序性。 
       当重排后,
CPU执行指令会乱序或并行运行,只有上面的happens-before所规定的情况下,才保证顺序性。 

   1》例如:有两个线程,thread1时不时去保存新对象,thread2:时不时去取新对象,由于没有设置syn对象锁,所以它俩可以同时执行,一前一后交叉执行。      

        当线程1保存 volatile hasNewObject值,它之前的object非变量也一起保存到主存:

        所以:当volatile前面有多个变量时,只需定义最后一个变量为volatile hasNewObject变量。

   下面put,take可以不用syn对象锁,也可以执行很好,即无锁操作,volatile变量+原子操作。

public class Exchanger {    private Object   object       = null;    private volatile hasNewObject = false;    public void put(Object newObject) {        while(hasNewObject) {            //wait - do not overwrite existing new object        }        object = newObject;        hasNewObject = true; //volatile write    }    public Object take(){        while(!hasNewObject){ //volatile read            //wait - don't take old object (or null)        }        Object obj = object;        hasNewObject = false; //volatile write        return obj;    }}

   2》错误,如果jvm重排以上指令,hasNewobject 排在object之前,哪么指令顺序出错问题来了,

        线程1写了hasNewobject后,这时线程2刚好看见hasNewobject =true,以为有新对象了,人家还没给新对象呢,所以取了空值。

while(hasNewObject) {    //wait - do not overwrite existing new object}hasNewObject = true; //volatile writeobject = newObject;

四、总结

       volatile variables  从主存直接读、写,比从cache读浪费更多的性能(cpu,主存),但是阻止指令重新排序提高了计算机性能。

       所以当我们用syn对象锁时,变量可见性有问题时,就用volatile 变量来维持原子操作,所以volatile也算是syn对象锁的一点小补充。

原创粉丝点击