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语言中最轻 ...
随机推荐
- 解决mysql登录报错ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)问题
问题描述: 在ubuntu14.04上安装完MYSQL后,MYSQL默认给分配了一个默认密码,但当自己在终端上使用默认密码登录的时候,总会提示一个授权失败的错误. 报错信息:Access denied ...
- ORB-SLAM2 论文&代码学习 —— 单目初始化
转载请注明出处,谢谢 原创作者:Mingrui 原创链接:https://www.cnblogs.com/MingruiYu/p/12358458.html 本文要点: ORB-SLAM2 单目初始化 ...
- printf函数中*修饰符的作用,如:%*d
在printf函数中,我们可以用数字修饰来控制打印的字段宽度和精度,如下(为强调视觉效果,均填充0): #include <stdio.h> int main() { ; float f= ...
- java 入门如何设计类
2019/12/24 | 在校大二上学期 | 太原科技大学 初学java后,我们会发现java难点不在于Java语法难学,而是把我们挂在了如何设计类的“吊绳”上了.这恰恰也是小白 ...
- LoaderTest加载测试用例的方法总结
加载用例,可以用suite.addTest(测试类名("测试函数名"))实现,也可用过suit.addTest(loader.Loader....)实现,一下针对Loader的三个 ...
- 【daily】文件分割限速下载,及合并分割文件
说明 主要功能: 1) 分割文件, 生成下载任务; 2) 定时任务: 检索需要下载的任务, 利用多线程下载并限制下载速度; 3) 定时任务: 检索可合并的文件, 把n个文件合并为完整的文件. GitH ...
- P2214 [USACO14MAR]哞哞哞Mooo Moo
链接:Miku ---------------------- 这道题还是个背包 --------------------- 首先看一下声音的组成,对于每一个农场的声音,它是由两部分组成的 :上一个农场 ...
- AGC011-C Squared Graph
题意 给定一个\(n\)个点\(m\)条边的图,构建一个\(n^2\)个点的图,新图的每个点都可以看成一个二元组,新图上的点\((a,b)和(a′,b′)\)之间有边,当且仅当原图中\((a,a′), ...
- PLSQL-Initialization error
1. 情况描述 安装完PLSQL之后,在写入数据库连接信息后,连接数据库,报Initialization error错误 (1)未安装Oracle数据库,无相应的oci.dll文件 (2)已安装数据库 ...
- 如何将下载的Jar包导入本地Maven仓库-sunziren
原创文章,转载请注明出处博客园! 昨天在打开一个Spring Boot项目的时候,发现pom.xml的文件图标上有个小红点,遂打开查看到底报的什么错. 原来是ojdbc14-10.2.0.4.0.ja ...