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语言中最轻 ...
随机推荐
- win7下彻底卸载和重装mysql
1 .目的:第一次安装完mysql后忘记了临时密码,通过各种途径都无法更改密码,因此不得不把mysql卸载了. 2 .建议:第一次安装mysql时会分配一个临时密码,如最后一行的se_:j<tq ...
- 【学习笔记】:JavaScript基础知识超详细总结!
目录 一.JavaScript的实现 二.JavaScript语言的特点 三.JS与HTML如何结合 四.JS中的数据类型 四.JS的原始数据类型 2.JS的引用数据类型 五.JS引用数据类型之函数 ...
- transient关键字和serialVersionUID
此文章很大部分转载于Java的架构师技术栈微信公众号,博主均测试通过加上自己理解写出 最近阅读java集合的源码,发现transient关键字,就了解了一下他的用法,transient关键字一般在实现 ...
- StackExchange.Redis 之 List队列 类型示例
//从第1个开始,依次向左插入值.如果键不存在,先创建再插入值 队列形式 先进后出,后进先出 //插入后形式 <-- 10,9,8,7,6,5,4,3,2,1 <-- 方向向左依次进行 ...
- 剑指offer-面试题65-不用加减乘除做加法-位运算
/* 题目: 在不使用加减乘除的前提下,计算两个整数之和. 思路: 不能使用加减乘除则只能考虑位运算. x=num1^num2,则为抹掉进位的结果. y=num1&num2,为只有进位的结果. ...
- 邓士鹏【MySql大全】
禁止使用系统关键字: typename 1.计算两个日期的时间差函数 SELECT TIMESTAMPDIFF(MONTH,'2009-10-01','2009-09-01'); interval可是 ...
- Dijkstra算法 1
// Dijkstra算法,适用于没有负边的情况 // 注意:是没有负边,不是没有负环 // 在这一条件下,可以将算法进行优化 // 从O(v*E)的复杂度,到O(V^2)或者是O(E*log(V)) ...
- 就不能换DB吗? 抽象工厂模式
15.1 就不能换DB吗? 15.2 最基本的数据访问程序 namespace 抽象工厂模式 { class Program { static void Main(string[] args) { U ...
- [CF1311A] Add Odd or Subtract Even
Solution a<b, delta=odd, ans=1 a<b, delta=even, ans=2 a=b ans=0 a>b, delta=odd, ans=2 a> ...
- Python小白
.IDLE软件为内建于CPython的集成开发环境(IDE),包括编辑器,编译或解释器,调试器 .py(后缀保存) 2.行一,单行注释 多行,””” ‘’’ 之后,内建 ...