• 轻松了解Volatile关键字


    Volatile关键字

    Volatile是java虚拟机提供的轻量级的同步机制。

    1、保证可见性

    可见性:举个例子,有三个线程A、B、C,假设A线程想要修改主内存中的一个数据num,因为每个线程都有自己的工作内存,想要修改数据的话,需要将num获得放到自己的工作内存,然后修改完成再返回给主内存。num在修改之前等于5,A线程修改之后变为8,当B线程或C线程再去拿num数据时,获得的是8而不是5,这就是保证了可见性。总结一句话就是,某一线程修改数据后,要立马通知其他线程自己修改了!

    public class JMMDemo {
        private volatile static int num = 0;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                while (num==0){
    
                }
            }).start();
            TimeUnit.SECONDS.sleep(3);
            num = 1;
            System.out.println(num);
    
        }
    }
    

    当不加volatile时,运行结果:

    3秒之后,main线程将num改为1,然而另一线程并不知道,所以进入死循环。

    加了volatile时,运行结果:

    3秒之后,main线程将num改为1,因为volatile保证了原子性,通知了另一线程自己做了修改,所以程序正常结束。

    2、不保证原子性

    原子性:指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。举个例子,你跟朋友在打篮球,期间总有场上的人手机响,搞得你们每次打起来了又得等他接电话,整场下来,就他事儿多,接了10多个电话,这就搞得你们这场篮球打的贼不爽,心情极差,一场篮球没有按自己想的那样漂亮的打下来,这就破坏了原子性。再举个例子,有一个add方法,就是加一操作,多个线程去调add方法,如果有20个线程,每个线程调1000次,正常情况下最终得到的结果应该是20x1000=20000。但是线程A和线程B拿到原始数据num后,线程A快一步,先将自己加完1的数据返回给主内存,当B在加完1写回主内存的时候拿的还是原始的数据并不是线程A加完1的数据,所以就会将A之前返回的数据覆盖,这样就会造成最终的结果小于20000。直接上代码!

    public class Demo {
        private static int num = 0;
    
        public static void add(){
            num++;
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    for (int j = 0; j < 2000; j++) {
                        add();
                    }
                }).start();
            }
            while (Thread.activeCount()>2){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+" "+num);
        }
    }
    

    不加volatile时,运行结果:

    加上volatile时,运行结果:

    从两种运行结果可以看出:volatile不能保证原子性!

    怎样能够保证原子性?

    方法1:可以加lock和synchronized保证原子性。

    方法2:使用原子类解决原子性问题

    public class Demo {
        private volatile static AtomicInteger num = new AtomicInteger();
    
        public static void add(){
            num.getAndIncrement();
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    for (int j = 0; j < 2000; j++) {
                        add();
                    }
                }).start();
            }
            while (Thread.activeCount()>2){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+" "+num);
        }
    }
    
    

    ​ 修改int类型为AtomicInteger类型,num.getAndIncrement()就等于num++。运行结果:

    结果意料之中,使用原子类解决原子性问题!

    3、防止指令重排

    什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。

    volatile关键字禁止指令重排序有两层意思:

    1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

    2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

    举个例子:

      //x、y为非volatile变量
    //flag为volatile变量
     
    x = 2;        //语句1
    y = 0;        //语句2
    flag = true;  //语句3
    x = 4;         //语句4
    y = -1;       //语句5
    

    ​ 由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会将语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的,并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

  • 相关阅读:
    1007 正整数分组
    Review: JQuery
    Summary: DOM modification techniques
    B
    D
    C
    hdu5592 倒序求排列+权值线段树
    主席树入门——询问区间第k大pos2104,询问区间<=k的元素个数hdu4417
    二维前缀和好题hdu6514
    莫比乌斯反演理解
  • 原文地址:https://www.cnblogs.com/leiger/p/13323484.html
Copyright © 2020-2023  润新知