java - jmm之volatile特性
volatile是什么?
volatile是JVM提供的一种轻量级的同步机制,其具有三个特性。
- 保证可见性
- 不保证原子性
- 禁止指令重排
保证可见性
JMM(java memory model)中文翻译为Java内存模型,是JVM下的一种规范,规定了JVM对于程序在内存中的变量应该以什么样的一样形式进行访问。
在JMM中对于同步则有以下的规定:
- 线程解锁前,必须把共享变量的值刷新回主内存。
- 线程加锁前,必须读取主内存的最新值到自己的工作内存中。
- 加锁与解锁需是同一把锁。
- 线程间的通信(传值)必须通过主内存来完成。

根据JMM中所规定的可知道每个执行线程都拥有各自线程的工作内存。对变量的计算操作实则是对各自工作内存内的变量副本进行操作,然后刷新到主内存中,而对于其他线程并不能感知到这个动作,仍然操作各自的工作内存内的变量副本,而此时就需要一种机制来通知每个线程说共享变量已经发生变动,不能在使用各自工作内存中旧的变量副本。这个机制就是volatile。
当某个变量被volatile修饰后,当一个线程修改了共享变量的值,其他线程能够立即感知这个修改。volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新该变量值。
另外除了volatile关键字之外,Java还有两个关键字能实现可见性,即synchronized和final。而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个这个引用访问到“初始化了一半”对对象),那其他线程中就能看见final字段的值。这句话的意思是被final修饰的字段在构造方法是初始化完毕会立马刷新到主内存中,当构造器并没有把this传递出去但是有发生了this引用逃逸(JVM优化导致的指令重排序导致引用逃逸)其他线程就有可能拿到this的引用但是其构造函数却还没构造完毕,而被final字段的值具有可见性,那么在其他线程中就能够实时看见final字段的值。
当越多线程共同争夺同一个变量时越容易出现变量不可见问题。
不保证原子性
这个相对比较好理解,每个线程在对于获取被volatile修饰的字段可以确保获取最新的值,但是由于在计算中并没有锁机制来确保1个时间点只进行一段计算而导致该线程准备更新该字段的值时候该其他线程对该字段设置的值直接覆盖导致数据错乱。所以对某个字段需要有一段的计算过程仅仅靠volatile是无法确保这段计算过程具有原子性,而是应该靠锁机制来确保。
| 顺序 | 线程A | 线程B |
|---|---|---|
| 1 | b=a+1 | |
| 2 | b=a+1 | |
| 3 | a=b | |
| 4 | a=b |
假设a为5那么即使a被volatile修饰着,他也只能确保线程在执行b=a+1时a的值是最新的值,但是当a未来得及设置进主内存中,线程A与线程B的b=a+1将等于6。接下来将6设置进主内存中,此时就发生数据错误。经过2个线程执行a的值应该是7而不是6。因此在计算过程中应该加上锁来避免这类情况发生。
禁止指令重排
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序,一般分为以下三种。

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑指令之间的数据依赖性。
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致是无法确定的,结果无法预测。
因为指令重排将会带来的问题:
A线程指令重排导致B线程出错。
在线程A中:
context = loadContext();
inited = true;
在线程B中:
while(!inited ){ //根据线程A中对inited变量的修改决定是否使用context变量
sleep(100);
}
doSomethingwithconfig(context);
假设线程A中发生了指令重排序:
inited = true;
context = loadContext();
那么B中很可能就会拿到一个尚未初始化或尚未初始化完成的context,从而引发程序错误。
指令重排导致单例模式失效。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
System.err.println("执行构造Singleton");
}
public static Singleton getInstance() {
if(instance == null) {
synchronzied(Singleton.class) {
if(instance == null) {
instance = new Singleton(); //非原子操作
}
}
}
return instance;
}
}
instance= new Singleton(),它并不是一个原子操作,它可以抽象为下面几条JVM指令:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM是可以针对它们进行指令的优化重排序的,经过重排序后如下:
memory =allocate(); //1:分配对象的内存空间
instance =memory; //3:instance指向刚分配的内存地址,此时对象还未初始化
ctorInstance(memory); //2:初始化对象
而此时instance已分配到实例地址即instance不为null,但是却未初始化对象也就是未执行构造方法进行初始化。此时另外一个线程进入该方法获取到未执行构造方法的对象进行使用就有可能导致程序报错。因此需要instance前修饰volatile来避免引用逃逸这类情况发生。
java - jmm之volatile特性的更多相关文章
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 深入理解Java内存模型JMM与volatile关键字
深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...
- java内存模型-volatile
volatile 的特性 当我们声明共享变量为 volatile 后,对这个变量的读/写将会很特别.理解 volatile 特性的一个好方法是:把对 volatile 变量的单个读/写,看成是使用同一 ...
- Java并发编程--Volatile详解
摘要 Volatile是Java提供的一种弱同步机制,当一个变量被声明成volatile类型后编译器不会将该变量的操作与其他内存操作进行重排序.在某些场景下使用volatile代替锁可以减少 ...
- 深入理解Java内存模型 - volatile
volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...
- 深入理解Java内存模型——volatile
volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会非常特别. 理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁 ...
- java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)
概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...
- java基础系列--volatile关键字
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/7833881.html 1.volatile简述 据说,volatile是java语言中最轻 ...
随机推荐
- 7个有用的JS技巧
就如其他的编程语言一样,JavaScript也具有许多技巧来完成简单和困难的任务. 一些技巧已广为人知,而有一些技巧也会让你耳目一新. 让我们来看看今天可以开始使用的七个JavaScript技巧吧! ...
- 幻读在 InnoDB 中是被如何解决的?
在MySQL事务初识中,我们了解到不同的事务隔离级别会引发不同的问题,如在 RR 级别下会出现幻读.但如果将存储引擎选为 InnoDB ,在 RR 级别下,幻读的问题就会被解决.在这篇文章中,会先介绍 ...
- Day5前端学习之路——盒模型和浮动
盒子模型 浮动float 一.盒子模型 (1)content内容区 width和height是框内容显示的区域——包括框内的文本内容,以及表示嵌套子元素的其他框,也可以使用min-width.max- ...
- 一起了解 .Net Foundation 项目 No.6
.Net 基金会中包含有很多优秀的项目,今天就和笔者一起了解一下其中的一些优秀作品吧. 中文介绍 中文介绍内容翻译自英文介绍,主要采用意译.如与原文存在出入,请以原文为准. .NET Micro Fr ...
- toj 4353 Estimation(树状数组+二分查找)
Estimation 时间限制(普通/Java):5000MS/15000MS 运行内存限制:65536KByte总提交: 6 测试通过: 1 描述 “There are ...
- 会话技术中的Cookie与session
关于会话技术 会话:一次会话中包含多次请求和响应. 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止 功能:在一次会话的范围内的多次请求间,共享数据 方式: 客户端会话技术:C ...
- WebAPI中的定时处理-使用Quartz.Net
借鉴: https://blog.csdn.net/lordwish/article/details/78926252 在最近的一篇文章中讲到了如何在web API中实现定时处理,采用的是比较原始的T ...
- 移动app
什么是移动App开发[重点] 苹果上的软件是如何开发出来的:使用IOS平台的开发工具和开发语言进行设计开发的!苹果上的开发语言:OC.Swift 安卓平台上的软件又是如何开发出来的:使用Java这么语 ...
- 百度架构师带你进阶高级JAVA架构,让你快速从代码开发者成长为系统架构者
百度架构师带你进阶高级JAVA架构,让你快速从代码开发者成长为系统架构者 1.
- 解决本地请求不到json问题,老是出现跨域
有时候我们需要在本地模拟请求json数据,但是老是出现谷歌跨域的问题, 网上找了方法,说是把json和HTML文件放在同一目录,还有的是页面引用时加上callback的. 但是不知道咋的,可能是本人不 ...