Volatile关键字

来源:互联网 发布:淘宝皇冠店铺100强 编辑:程序博客网 时间:2024/06/01 07:49

前言


Volatile是一个关键字,也是一种同步的策略,它的出现是为了解决一个叫做内存可见性问题的,那首先就要来看一下,什么叫做内存可见性问题

内存可见性问题


我们先看一下一段代码

package com.pochi.juc;public class VisibilityProblem {    public static void main(String[] args) {        ThreadExample threadExample = new ThreadExample();        // 多线程开启        new Thread(threadExample).start();        while (true){            // 只要探测到另外那个线程的flag变成true,就打印,跳出            if (threadExample.isFlag()){                System.out.println("--------------");                break;            }        }    }}// 一个Runnable实现类,准备多线程class ThreadExample implements  Runnable{    // 这个变量是放在堆中的,多线程共享    private boolean flag=false;    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }    @Override    public void run() {        try {            // 线程启动之后,停顿0.25s            Thread.sleep(250);            // 然后就把flag改成true了            if(!isFlag()){                setFlag(true);                System.out.println("flag="+isFlag());            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

结果

flag=true(程序一直没有停...)

所以,其实这是一个问题,明明我在另一个Thread里面已经把flag的值改了,Main线程为什么还是一直在无限循环呢?

寻根问源


其实JVM会把内存划分成几个部分,除了我们常见的那种栈、堆…的划分,其实为了各线程能快速的访问堆的数据,还会给各线程分一点缓存的空间。例如,我们看下图:

这里写图片描述

这个时候主存里面有一个flag,然后会分给两个不同的线程,相当于一个复制,所以在这三个地方都会有Flag了。

这里写图片描述

后来,Thread1在睡了0.25s,改flag改成了true,并且告诉了主存,我改了true了,你也改吧。主存也改了。

这里写图片描述

那我们Main这个线程去拿一下新的Flag值不就行了?嘿嘿,想的美!

这里写图片描述

我们在Main线程里面做的是

while (true){    // 只要探测到另外那个线程的flag变成true,就打印,跳出    if (threadExample.isFlag()){        System.out.println("--------------");        break;     }}

while(true)这个循环语法,底层实现的优先级别是很高的,因此,使得它一直在那儿循环腾不出手,去主存里面取那个flag。

那怎么办?怎么解决?

最简单的方法就是,加锁。

while (true){    synchronized (threadExample){        if (threadExample.isFlag()){           System.out.println("--------------");           break;        }    }}

结果

--------------flag=true

每次while判断完,都还要去看看锁到了没有,那就能腾出手去拿主存的东西啦

这里写图片描述

你觉得问题已经解决了?对的,其实是已经解决了,但是不完美,为啥?这个内存可见性问题,杀鸡焉用牛刀?其实加锁是效率最低的处理方式,因为要阻塞,阻塞就是等待,就是

这里写图片描述

开个网页,瞎转圈,转圈的时候怎么办?两种方案:

  1. 等(费时20s);
  2. 刷刷微博、朋友圈、开把农药(费时???)。

由上例可见,阻塞是多么讨厌。

所以所以,有什么办法?

Volatile!

费老大劲,说到这个,累死我了。

这里写图片描述

Volatile


如果不想知道为什么的大佬,看下面怎么用就行了。

怎么使?

package com.pochi.juc;public class VisibilityProblem {    public static void main(String[] args) {        ThreadExample threadExample = new ThreadExample();        new Thread(threadExample).start();        while (true){                if (threadExample.isFlag()){                    System.out.println("--------------");                    break;                }        }    }}class ThreadExample implements  Runnable{    /***********************************************        我 特 地 搞 这 么 长 就 是 为 了 引 起 你 的 注意    *************************************************/    // 就多加了一个volatile关键字,其他没变    private volatile boolean flag=false;    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }    @Override    public void run() {        try {            Thread.sleep(250);            if(!isFlag()){                setFlag(true);                System.out.println("flag="+isFlag());            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

结果

必须好使啊,不信你自己试试?

--------------flag=true

为什么Volatile这么6?

你这可是为难我了,其实我也是不知道的,但是为了写出来,我得知道。

Ok,我查了一圈资料回来了,总结一下,Volatile这么6的原因这样的。
我来解释一下,原来我们说了,之所以,不加锁,不加volatile关键字,我们的while一直无限循环的原因,是while级别好高,速度好快,腾不出手来看主存里面现在的值。而volatile的做法就是,强制

加了volatile的变量,在读和写操作的时候,都会先和主存做下交互,比如每次写完,会及时刷回到主存里面去,要读之前,先到主存拿最新了。通俗来说,就是每次做,都腾出手多做点事儿。

这么整,效率怎么样?低肯定会是低一点,毕竟多做了事儿。但是比等待要好一些。

你觉得结束了?

这里写图片描述

这么多做一两行代码的事儿,可不是最重要的效率低的原因,效率真正下降的原因,是volatile禁止指令重排。想进一步了解指令重排的朋友,可以详细看看参考资料。简单来说,就是JVM在不影响逻辑的前提下,把执行的顺序变变,当然这个变变是能提高效率的变变。

dobule b=1.5;int a=5;

这两句话,谁放前面,谁放后面是没有问题的,那JVM就根据怎么放效率高就怎么放咯~

volatile 禁止指令重排

volatile其实是通过内存屏障的方式实现上面我说的那些“腾出手”,具体来说是:

  • 在每个volatile写操作的前面插入一个StoreStore屏障

  • 在每个volatile写操作的后面插入一个SotreLoad屏障

  • 在每个volatile读操作的后面插入一个LoadLoad屏障

  • 在每个volatile读操作的后面插入一个LoadStore屏障

这里写图片描述

这里写图片描述

所以其实,volatile的效率只要是消耗在了这里。

volatile 总结


  1. volatile 可以保证内存可见性

  2. volatile 是一种轻量级的同步机制

    正所谓的轻量级,就是说不加锁,消耗没有那么大。

  3. volatile 不保证原子性

    所以,volatile的使用一定要确保,只要一个线程会对这个变量做出改变,多个的话,还是用锁。用了锁就不要用volatile了。

参考文献


http://blog.csdn.net/liuguangqiang/article/details/52154011
http://blog.csdn.net/hqq2023623/article/details/51013468
http://blog.csdn.net/qq_29923439/article/details/51273812

原创粉丝点击