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){
            // 只要探测到另外那个线程的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值