java 基础 --- volatile
问题 :
- volatile 解决的是什么问题
- 有什么应用场景
概述
某些共享变量的时候我们使用volatile 修饰,它会保证修改的值立即被更新到主存,或是从主存中获取最新的值。它的底层是如何实现的?
volatile 使用场景
通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:
1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;
2、该变量没有包含在具有其它变量的不变式中。(出处)
下面列出两个例子,都使用了volatile
1. 状态标记量
public class ServerHandler {
private volatile isopen;
public void run() {
if (isopen) {
//促销逻辑
} else {
//正常逻辑
}
}
public void setIsopen(boolean isopen) {
this.isopen = isopen
}
}
多个线程下,为使得isopen 这个值最新,我们使用了 volatile .
2、double check
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
syschronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这个单例模式下,例如A线程正在初始化对象,但是没初始化好(局部成员没初始化好),此时B 线程来了,此时判断对象已经不为空了,那么就返回了一个错误的对象。这个时候可以使用volatile .
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
syschronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
如何保证内存可见性?
下面这段描述来自 : https://www.jianshu.com/p/195ae7c77afe ,非原创
在java虚拟机的内存模型中,有主内存和工作内存的概念,每个线程对应一个工作内存,并共享主内存的数据,下面看看操作普通变量和volatile变量有什么不同:
1、对于普通变量:读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值。
2、对于volatile变量,读操作时JMM会把工作内存中对应的值设为无效,要求线程从主内存中读取数据;写操作时JMM会把工作内存中对应的数据刷新到主内存中,这种情况下,其它线程就可以读取变量的最新值。
volatile变量的内存可见性是基于内存屏障(Memory Barrier)实现的,什么是内存屏障?内存屏障,又称内存栅栏,是一个CPU指令。在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM(Java Memory Modal)为了保证在不同的编译器和CPU上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。这个地方我们回想一下上面的单利模式下的 double-check ,正是由于内存不可见性导致了错误。
下面的章节我们将会介绍原理。
volatile 原理
了解volatile原理前,我们需要了解重排序。
重排序
编译器为了快速地完成编译工作,优化了代码顺序,即是说有些代码本来在前的有可能在后编译,相反也是有可能的。
public class VolatileTest { int a = 0;
int b = 0; public void set() {
a = 1;
b = 1;
} public void loop() {
while (b == 0) continue;
if (a == 1) {
System.out.println("i'm here");
} else {
System.out.println("what's wrong");
}
}
}
两线程分别执行set 和loop ,结果会是怎么样?不一定,这里涉及到了编译器的重排序和CPU的重排序。CPU 的重排序是怎么回事呢?可以先看一下下面图 :
可以看到 CPU 和 L1之前存在LoadBuff 和 StoreBuffer ,它们可以认为是又多一级缓存,具体的工作如下
1、CPU执行load读数据时,把读请求放到LoadBuffer,这样就不用等待其它CPU响应,先进行下面操作,稍后再处理这个读请求的结果。
2、CPU执行store写数据时,把数据写到StoreBuffer中,待到某个适合的时间点,把StoreBuffer的数据刷到主存中。因为StoreBuffer的存在,CPU在写数据时,真实数据并不会立即表现到内存中,所以对于其它CPU是不可见的;同样的道理,LoadBuffer中的请求也无法拿到其它CPU设置的最新数据;
由于StoreBuffer和LoadBuffer是异步执行的,所以在外面看来,先写后读,还是先读后写,没有严格的固定顺序。
所以由于CPU的更新不同步导致有可能读到过时的信息。
源码解析
有如下代码
public class VolatileTest {
static volatile int num;
public static void main(String[] args) {
num = 5;
}
}
再使用 javap –v 指令,看一下编译的字节码。
我们看到了比平时多了一个标志 : ACC_VOLATILE ,而putstatic和不加volatile 的情况是一样的,那么我们知道在使用volatile后会去获取最新的,那么真实的实现逻辑会不会在 putstatic 这上面呢?
下面也是根据狼哥的文章,自己找到执行方法的地方。下面我们来看看这一句到底执行了什么
bytecodeInterpreter.cpp 文件下,
我们主要看这三个地方,首先判断 cache –> is_volatile ,是不是volatile 修饰的,然后进入release_int_field_put 方法
,我们看一下这个方法 ,
再调用 release_store
遇到我们看到了使用 volatile 的形参,这里的volatile 是C++ ,逻辑就是赋值而已,那么C++ 的volatile 的作用是什么呢?
c/c++中的volatile关键字,用来修饰变量,通常用于语言级别的 memory barrier,在"The C++ Programming Language"中,对volatile的描述如下:
A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.
紧接着执行OrderAccess::storeload()
,这又是啥?
其实这就是经常会念叨的内存屏障,之前只知道念,却不知道是如何实现的。从CPU缓存结构分析中已经知道:一个load操作需要进入LoadBuffer,然后再去内存加载;一个store操作需要进入StoreBuffer,然后再写入缓存,这两个操作都是异步的,会导致不正确的指令重排序,所以在JVM中定义了一系列的内存屏障来指定指令的执行顺序。
JVM中定义的内存屏障如下,JDK1.7的实现
总结
- volatile 只保证了“可见性”,不能保证原子性,典型的例子就是 i++ ,只保证了下一次读和写都将会去内存中拿最新的值
- volatile 可以避免编译器的重排序,底层的实现是JVM 制定的内存屏障
- volatile 相对于锁有良好的性能
参考资料
- 狼哥_volatile
- https://www.ibm.com/developerworks/java/library/j-jtp06197
- https://www.zhihu.com/question/49656589
- http://g.oswego.edu/dl/jmm/cookbook.html(推荐一看)
java 基础 --- volatile的更多相关文章
- Java基础 - volatile
volatile的作用:对与volatile修饰的变量, 1,保证该变量对所有线程的可见性. 2,禁止指令重排序. Java内存模型(JMM) 原子性 i = 2; 把i加载到工作内存副本i,副本i= ...
- Java基础教程:多线程杂谈——双重检查锁与Volatile
Java基础教程:多线程杂谈——双重检查锁与Volatile 双重检查锁 有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实 ...
- [Java面经]干货整理, Java面试题(覆盖Java基础,Java高级,JavaEE,数据库,设计模式等)
如若转载请注明出处: http://www.cnblogs.com/wang-meng/p/5898837.html 谢谢.上一篇发了一个找工作的面经, 找工作不宜, 希望这一篇的内容能够帮助到大 ...
- 最适合作为Java基础面试题之Singleton模式
看似只是最简单的一种设计模式,可细细挖掘,static.synchronized.volatile关键字.内部类.对象克隆.序列化.枚举类型.反射和类加载机制等基础却又不易理解透彻的Java知识纷纷呼 ...
- Java基础知识【下】( 转载)
http://blog.csdn.net/silentbalanceyh/article/details/4608360 (最终还是决定重新写一份Java基础相关的内容,原来因为在写这一个章节的时候没 ...
- Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)
线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...
- Java基础常见英语词汇
Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的 ['prəʊɡræmɪŋ]编程 OO: object ...
- java 基础题 很基础, 很有趣
都是一些非常非常基础的题,是我最近参加各大IT公司笔试后靠记忆记下来的,经过整理献给与我一样参加各大IT校园招聘的同学们,纯考Java基础功底, 老手们就不用进来了,免得笑话我们这些未出校门的孩纸们, ...
- JAVA基础知识(转)
本文就java基础部分容易混淆的一些知识点进行了一下总结.因为Java本身知识点非常多,不可能在很短的篇幅就能叙述完,而且就某一个点来讲,如欲仔细去探究,也能阐述的非常多.这里不做全面仔细的论述,仅做 ...
随机推荐
- Android 获取 content layout
if (findViewById(android.R.id.content) instanceof ViewGroup) { ViewGroup mainView = ((ViewGroup)find ...
- 注解中用于@target的方法annotation/--ElementType.METHOD,ElementType.TYPE对应方法,类接
@Target: @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages.types(类.接口.枚举.Annotation类型).类型成员(方法.构造 ...
- JavaScript基础总纲
如果前人种好了树那我们干嘛不去享受阴凉,然后花费时间去为大树的成长进一份力. 我发现一个站点写的很全面写很系统,我总结主要分为一些几个模块: 一,JavaScript 教程(基础) 二,JavaScr ...
- c++之选择排序和冒泡排序实现
1.冒泡排序 冒泡排序就是通过对比前一个和后一个数的大小,按照规则进行顺序的调换.每一轮对比之后最大或者最小值都会浮到最上面或者沉到最低下. 如:对这一数组进行冒泡排序:int a[5]{34,12 ...
- python要点记录
1.字典:当存储的key数目在几万到几十万之间效率最高.
- TX2 之tensorflow环境部署
刷机jetpack3.3 首先TX2必须是3.3版本的jetpack,因为截止目前nvidia发布的tensorflow只支持3.3版本的jetpack,刷机的具体步骤可以参考NVIDIA Jetso ...
- 直接线性变换解法(DLT)用于标定相机
直接线性变换法是建立像点坐标和相应物点物方空间坐标之间直接的线性关系的算法.特点:不需要内外方位元素:适合于非量测相机:满足中.低精度的测量任务:可以标定单个相机. 1 各坐标系之间的关系推导直接线性 ...
- [Re:从零开始的分布式] 0.x——分布式锁概述
为什么需要分布式锁 Martin Kleppmann是英国剑桥大学的分布式系统的研究员,Martin认为一般我们使用分布式锁有两个场景: 效率:使用分布式锁可以避免不同节点重复相同的工作,这些工作会浪 ...
- HDU - 6096 处理后缀的字典树
题意:给定n个字符串,m次询问,每次询问多少个字符串前缀是pre且后缀是suf,前后缀不可相交 字典树同时存储前后缀,假设字符串长为len则更新2*len个节点,依次按s[0],s[len-1],s[ ...
- java无符号Byte
1.无符号byte, 实现了将byte(-128~127) 转换为 (0~255) class UnsignedByte { private short value; private byte raw ...