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学习之Adapter(数据适配器)
1.定义 数据适配器是AdapterView视图(如ListView - 列表视图控件.Gallery - 缩略图浏览器控件.GridView - 网格控件.Spinner - 下拉列表控件. ...
- 【ocp-12c】最新Oracle OCP-071考试题库(46题)
46.(10-4) choose two: Examine the data in the CUST_NAME column of the CUSTOMERS table. CUST_NAME --- ...
- 学习笔记|JSP教程|菜鸟教程
学习笔记|JSP教程|菜鸟教程 ------------------------------------------------------------------------------------ ...
- django 模型中 class Meta 内 各种属性的用法
Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性.下面对此作一总结: abstract 这个属性是定义当前的模型类是不是一个抽象类.所谓抽象类是不会相应数据库表的 ...
- CTF常见加密方式汇总
1.栅栏密码 在IDF训练营里做过一道关于栅栏密码的问题. 栅栏密码的解法很简单,也有点复杂,字符长度因数多得会有很多个密码.对,栅栏密码的解法就是:计算该字符串是否为合数,若为合数,则求出该合数除本 ...
- jQuery入门讲解
jQuery设计思想: (1)选择页面元素 css选择器: $(document) 选择整个文档对象, $("#myId") 选择id为myId的页面元素, $("div ...
- 【原】[UIImage imageWithContentsOfFile:]引发的图片无法显示的问题
最近在做一个iOS手机项目的时候,遇到一个奇怪的问题,这里跟大家分享一下. 一.问题重现 1.启动App后,通过http请求下载了一个1.jpg文件到Cache目录下,下载成功之后,将图片显示在界面上 ...
- java内存的分配策略
1.概述 本文是<深入理解java虚拟机>(周志明著)3.6节的笔记整理,文章结构也与书上相同,讲述的是几条最普遍的内存分配策略. 2.对象优先在Eden分配 ** 大多数情况下,对象在新 ...
- .crx 文件修改
.crx 文件类型:Chrome Extension 扩展名为.crx的文件是一个插件文件. 解压:使用7zip 修改: notepad++ 打包: Chrome 扩展项
- cool kickass
I can stay like this alllllllllll daaaaaaaaayyyyyy.