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判断完,都还要去看看锁到了没有,那就能腾出手去拿主存的东西啦
你觉得问题已经解决了?对的,其实是已经解决了,但是不完美,为啥?这个内存可见性问题,杀鸡焉用牛刀?其实加锁是效率最低的处理方式,因为要阻塞,阻塞就是等待,就是
开个网页,瞎转圈,转圈的时候怎么办?两种方案:
- 等(费时20s);
- 刷刷微博、朋友圈、开把农药(费时???)。
由上例可见,阻塞是多么讨厌。
所以所以,有什么办法?
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 总结
volatile 可以保证内存可见性
volatile 是一种轻量级的同步机制
正所谓的轻量级,就是说不加锁,消耗没有那么大。
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
- volatile关键字
- 关键字volatile
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- 关键字volatile
- volatile关键字
- volatile关键字
- volatile 关键字
- 关键字 volatile
- volatile关键字
- Python编程:从入门到实践(课后习题7)
- Gradle多环境多渠道打包
- 关于在线机器学习ftrl_proximal_lr的二三件事
- Cocos2d-x启动图片
- SQL Server 2016 详细安装过程
- Volatile关键字
- 【安全牛学习笔记】MANAGEMENT FRAME 管理帧
- 一个薅羊毛解决的框架,因为内存占用太大,没实验。。
- vue+webpack项目创建过程
- 使用碰撞指针解决LeetCode问题:SortColor75,TwoSumII167,ReverseString344,reverseVowels345
- 排列组合
- Python编程:从入门到实践(课后习题8)
- 内部类与外部类之间访问总结
- 测试模型之W模型