volatile关键值
happens-before原则
我们编写的程序都要经过优化后(编译器和处理器会对我们的程序进行优化以提高运行效率)才会被运行,优化分为很多种,其中有一种优化叫做重排序,重排序需要遵守happens-before规则,换句话说只要满足happens-before原则就可以进行重排序。
定义:在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系
注意:定义中所说的前一个操作happens-before后一个操作并不是说前一个操作必须要在后一个操作之前执行,而是指前一个操作的执行结果必须对后一个操作可见,考虑下述情况:
int a = 1; //操作A
int b = 2; //操作B
单线程执行上述代码块规定操作A happens-before 操作B,也就是说操作A的结果对操作B是可见的,但是操作B对操作A中a=1的赋值并没有依赖,即使操作A与操作B重排序了,它们之间的happens-before关系仍然存在,这个例子就说明了happens-before并不是对执行顺序对约束,同时也是重排序的一种情况。
规则:
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
volatile关键字
可见性
volatile修饰的变量的一个特点是可见性:保证被volatile修饰的共享变量对所有线程可见,也就是当一个线程修改了一个被volatile修饰变量的值,其他线程可以立即得知新值,举例:
volatile boolean shutdownRequested;
public void shutdown(){
shutdownRequested = true;
}
public void doWork(){
while(!shutdownRequested){
//do stuff
}
}
错误用法:
public class VolatileVisibility {
public static volatile int i =0;
public static void increase(){
i++;
}
}
/**
volatile关键值不保证有序性,i++包括读取一个值,然后写回一个新值,新值比原来值加了1,这相当于两个步骤,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的值,并进行加一操作,会发生更新重复,存在线程安全问题
**/
有序性
volatile修饰的变量的另一个特是有序性点:禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象
//双重校验锁
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
疑问:上述代码Singleton变量为什么要用volatile修饰?
解答:
instance = new Singleton()可以分为下述步骤完成:
memory = allocate(); //1:分配对象的内存空间
instance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
由于2,3步骤没有数据依赖关系,因此2,3可以重排序并没有违背单线程的happens-before规则,重排后如下:
memory = allocate(); //1.分配对象内存空间
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory); //2.初始化对象
根据volatile变量的可见性,在执行完3后,instance不为空,但是尚未实例化,但是此时如果有线程过来请求实例,就可能返回尚未实例化对象。
内存屏障
缓存一致性

嗅探机制(snooping):所有内存传输都发生在一条共享的总线上,而所有的处理器都能看到这条总线:缓存本身是独立的,但是内存是共享资源,嗅探(snooping)协议的思想是,缓存不仅仅在做内存传输的时候才和总线打交道,而是不停地在嗅探总线上发生的数据交换,跟踪其他缓存在做什么。所以当一个缓存代表它所属的处理器去读写内存时,其他处理器都会得到通知,它们以此来使自己的缓存保持同步。只要某个处理器一写内存,其他处理器马上就知道这块内存在它们自己的缓存中对应的段已经失效。
总线锁机制(lock):在指令前面加上lock,那么会锁住总线和相应的缓存,其他指令会被阻塞,当lock后的指令执行完毕会将结果刷新到内存中去,根据嗅探机制,其他cpu中的缓存会失效,重新从内存中读取,也就解决了缓存一致性问题
缓存一致性协议(MESI):cpu缓存有四个标记位:
M: Modify,修改缓存,当前CPU的缓存已经被修改了,即与内存中数据已经不一致了
E: Exclusive,独占缓存,当前CPU的缓存和内存中数据保持一致,而且其他处理器并没有可使用的缓存数据
S: Share,共享缓存,和内存保持一致的一份拷贝,多组缓存可以同时拥有针对同一内存地址的共享缓存段
I: Invalid,失效缓存,这个说明CPU中的缓存已经不能使用了
CPU的读取遵循下面几点:
如果缓存状态是I,那么就从内存中读取,否则就从缓存中直接读取。
如果缓存处于M或E的CPU读取到其他CPU有读操作,就把自己的缓存写入到内存中,并将自己的状态设置为S。
只有缓存状态是M或E的时候,CPU才可以修改缓存中的数据,将其他cpu缓存设置无效,修改后,缓存状态变为M
内存屏障
- 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
- 内存屏障有两个作用:
阻止屏障两侧的指令重排序;
强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
- 对于Load Barrier来说,在指令前插入LoadBarrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;对应的在读volatile变量前加上Lfence
- 对于Store Barrier来说,在指令后插入StoreBarrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见,对应的在写volatile变量后加上Sfence
参考资料
http://blog.csdn.net/u010031673/article/details/48153797
https://kb.cnblogs.com/page/504824/
https://www.cnblogs.com/dolphin0520/p/3920373.html
https://www.jianshu.com/p/195ae7c77afe
http://blog.csdn.net/iter_zc/article/details/42006811
https://www.jianshu.com/p/2ab5e3d7e510
volatile关键值的更多相关文章
- SAP BW 例程(Routine)【开始例程、关键值或特性的例程、结束例程】
定义 可以使用例程定义关键值或特性的复杂的转换规则. 例程是本地 ABAP 类,它们包括预定义的定义和实施范围.进站和出站参数的 TYPES及方法签名都存储在定义范围中.实际例程创建于实施范围中.使用 ...
- uva11078 - Open Credit System(动态维护关键值)
这道题使用暴力解法O(n*n)会超时,那么用动态维护最大值可以优化到O(n).这种思想非常实用. #include<iostream> #include<cstdio> #in ...
- Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
- java中关键字volatile的作用
用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况.volatile就是用来 ...
- 【转】Java并发编程:volatile关键字解析
转自:http://www.importnew.com/18126.html#comment-487304 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备 ...
- [转]Java学习日记之 volatile
用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况.volatile就是用来 ...
- volatile与synchronized关键字
volatile关键字相信了解Java多线程的读者都很清楚它的作用.volatile关键字用于声明简单类型变量,如int.float.boolean等数据类型.如果这些简单数据类型声明为volatil ...
- (转)Java并发编程:volatile关键字解析
转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...
- volatile关键字解析
转载:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受 ...
随机推荐
- 「雅礼集训 2017 Day2」解题报告
「雅礼集训 2017 Day2」水箱 我怎么知道这种题目都能构造树形结构. 根据高度构造一棵树,在树上倍增找到最大的小于约束条件高度的隔板,开一个 \(vector\) 记录一下,然后对于每个 \(v ...
- underscore.js源码研究(7)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- Swift5 语言参考(九) 泛型和参数
本章介绍泛型类型,函数和初始值设定项的参数和参数.声明泛型类型,函数,下标或初始化程序时,可以指定泛型类型,函数或初始化程序可以使用的类型参数.当创建泛型类型的实例或调用泛型函数或初始化程序时,这些类 ...
- 微信小程序自定义组件的使用以及调用自定义组件中的方法
在写小程序的时候,有时候页面的内容过多,逻辑比较复杂,如果全部都写在一个页面的话,会比较繁杂,代码可读性比较差,也不易于后期代码维护,这时候可以把里面某部分功能抽出来,单独封装为一个组件,也就是通常说 ...
- redis常用命令(一)
一.redis常见的数据操作命令 http://redisdoc.com/ 二.键(key) keys *: 查询所有的key. exists key:判断某个key是否存在. move key db ...
- 好用的在线工具汇总:Iconfont图标,数据mock,时间函数库,颜色查询 等
一 时间函数库 ———http://momentjs.com/ 非常全的时间处理函数库,引入使用非常方便. 二 Iconfont———http://www.iconfont.cn/ 各种小图标 ...
- 全网最详细的zkfc启动以后,几秒钟以后自动关闭问题的解决办法(图文详解)
不多说,直接上干货! 问题详情 情况描述如题所示,zkfc启动以后,几秒钟以后自动关闭. 解决办法: 1.检查下每台机器的时间是否同步: 2.检查下每台机器的防火墙是否关闭: 3.查看zkfc的日志路 ...
- 阿里巴巴Java开发手册———总结
前言 阿里巴巴Java开发手册———个人追加的见解和补充(一) 阿里巴巴Java开发手册———个人追加的见解和补充(二) 阿里巴巴Java开发手册———个人追加的见解和补充(三) 阿里巴巴Java开发 ...
- SpringCloud入门之Maven系统安装及配置
一.Maven 介绍 这个单词中文翻译为“专家”或“内行”.下面将向你介绍 Maven这一跨平台的项目管理工具.作为 Apache 组织中的一个成功的开源项目,Maven 主要服务于基 Java 平台 ...
- Maven Jetty8
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactI ...