你真的了解volatile吗,关于volatile的那些事
很早就接触了volatile,但是并没有特别深入的去研究她,只有一个朦胧的概念,就是觉得
用她来解决可见性的,但可见性又是什么呢?
最近经过查阅各种资料,并结合自己的思考和实践,对volatile有了比较深刻的认识,
在此总结并分享给大家。
可见性
如何理解可见性,还是来看个会出现死循环的例子:
(注意:运行时请加上jvm参数:-server,while循环内不要有标准输出):
这是为什么呢?先来看看java的内存模型,如下图:
java内存分为工作内存和主存
工作内存:即java线程的本地内存,是单独给某个线程分配的,存储局部变量等,同时也会复制主存的共享变量作为本地
的副本,目的是为了减少和主存通信的频率,提高效率。
主存:存储类成员变量等
可见性是指的是线程访问变量是否是最新值。
局部变量不存在可见性问题,而共享内存就会有可见性问题,
因为本地线程在创建的时候,会从主存中读取一个共享变量的副本,且修改也是修改副本,
且并不是立即刷新到主存中去,那么其他线程并不会马上共享变量的修改。
因此,线程B修改共享变量后,线程A并不会马上知晓,就会出现上述死循环的问题。
解决共享变量可见性问题,需要用volatile关键字修饰。
如下图代码就不会出现死循环:
那么为什么能解决死循环的问题呢?
可见性的特性总结为以下2点:
1. 对volatile变量的写会立即刷新到主存
2. 对volatile变量的读会读主存中的新值
可以用如下图更清晰的描述:

如此一来,就不会出现死循环了。
为了能更深刻的理解volatile的语义,我们来看下面的时序图,回答这2个问题:

问题1:t2时刻,如果线程A读取running变量,会读取到false,还是等待线程B执行完呢?
答案是否定的,volatile并没有锁的特性。
问题2:t4时刻,线程A是否一定能读取到线程B修改后的最新值
答案是肯定的,线程A会从重新从主存中读取running的最新值。
还有一种办法也可以解决死循环的问题:

虽然running变量上没有volatile关键字修饰,但是读和写running都是同步方法
同步块存在如下语义:
1.进入同步块,访问共享变量会去读取主存
2.退出同步块,本地内存对共享变量的修改会立即刷新到主存
因此上述代码不会出现死循环。
volatile变量的原子性
我看了很多文章,有些文章甚至是出版的书籍都说volatile不是原子的,
他们举的例子是i++操作,i++本身不是原子操作,是读并写,我这里要讲的原子性
指的是写操作,原子性的特别总结为2点:
1. 对一个volatile变量的写操作,只有所有步骤完成,才能被其它线程读取到。
2. 多个线程对volatile变量的写操作本质上是有先后顺序的。也就是说并发写没有问题。
这样说也许读者感觉不到和非volatile变量有什么区别,我来举个例子:
//线程1初始化User
User user;
user = new User();
//线程2读取user
if(user!=null){
user.getName();
}
在多线程并发环境下,线程2读取到的user可能未初始化完成
具体来看User user = new User的语义:
1:分配对象的内存空间
2:初始化对线
3:设置user指向刚分配的内存地址
步骤2和步骤3可能会被重排序,流程变为
1->3->2
这些线程1在执行完第3步而还没来得及执行完第2步的时候,如果内存刷新到了主存,
那么线程2将得到一个未初始化完成的对象。因此如果将user声明为volatile的,那么步骤2,3
将不会被重排序。
下面我们来看一个具体案例,一个基于双重检查的懒加载的单例模式实现:

这个单例模式看起来很完美,如果instance为空,则加锁,只有一个线程进入同步块
完成对象的初始化,然后instance不为空,那么后续的所有线程获取instance都不用加锁,
从而提升了性能。
但是我们刚才讲了对象赋值操作步骤可能会存在重排序,
即当前线程的步骤4执行到一半,其它线程如果进来执行到步骤1,instance已经不为null,
因此将会读取到一个没有初始化完成的对象。
但如果将instance用volatile来修饰,就完全不一样了,对instance的写入操作将会变成一个原子
操作,没有初始化完,就不会被刷新到主存中。
修改后的单例模式代码如下:

对volatile理解的误区

如果i++的操作是线程安全的,那么预期结果应该是i=20000
然而运行的结果是:11349
说明i++存在并发问题
i++语义是i=i+1
分为2个步骤
步骤1:读取i=0
步骤2:计算i+1=1,并重新赋值给i
那么可能存在2个线程同时读取到i=0,并计算出结果i=1然后赋值给i
那么就得不到预期结果i=2。
这个问题说明了2个问题:
1.i++这种操作不是原子操作
2.volatile 并不会有锁的特性
你真的了解volatile吗,关于volatile的那些事的更多相关文章
- [CareerCup] 13.5 Volatile Keyword 关键字volatile
13.5 What is the significance of the keyword "volatile" in C 这道题考察我们对于关键字volatile的理解,顾名思义, ...
- java 用volatile和不用volatile的区别
在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写.这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值 ...
- 多线程的指令重排问题:as-if-serial语义,happens-before语义;volatile关键字,volatile和synchronized的区别
一.指令重排问题 你写的代码有可能,根本没有按照你期望的顺序执行,因为编译器和 CPU 会尝试指令重排来让代码运行更高效,这就是指令重排. 1.1 虚拟机层面 我们都知道CPU执行指令的时候,访问内存 ...
- 【C# 线程】 volatile 关键字和Volatile类、Thread.VolatileRead|Thread.VolatileWrite 详细 完整
overview 同步基元分为用户模式和内核模式 用户模式:Iterlocked.Exchange(互锁).SpinLocked(自旋锁).易变构造(volatile关键字.volatile类.Thr ...
- volatile和不加volatile的区别
http://blog.chinaunix.net/uid-25100840-id-3067899.html 对于一个地址,如果加上了volatile参数,这个地址也就不会经过编译器优化,也就是说这个 ...
- synchronized 与 volatile 区别 还有 volatile 的含义
熟悉并发的同学一定知道在java中处理并发主要有两种方式: 1,synchronized关键字,这个大家应当都各种面试和笔试中经常遇到. 2,volatile修饰符的使用,相信这个修饰符大家平时在项目 ...
- 你真的了解volatile关键字吗?
volatile关键字经常在并发编程中使用,其特性是保证可见性以及有序性,但是关于volatile的使用仍然要小心,这需要明白volatile关键字的特性及实现的原理,这也是本篇文章的主要内容. 一. ...
- 你真的了解 volatile 关键字吗?
今天,让我们一起来探讨 Java 并发编程中的知识点:volatile 关键字 本文主要从以下三点讲解 volatile 关键字: volatile 关键字是什么? volatile 关键字能解决什么 ...
- [Java并发编程(三)] Java volatile 关键字介绍
[Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...
- 深入理解volatile
volatile知识点 --------------------------------------------------------------------------- 1.volatile关键 ...
随机推荐
- javascript的字符串操作
一,把字符串的首字母大写返回一个新的字符串 1.1简单写法,把一个单词的首字母大写 String.prototype.firstUpperCase = function(){ return this[ ...
- SVG的path的使用
SVG的path的使用: 参考:http://justcoding.iteye.com/blog/2226354 <%@ page language="java" conte ...
- 在ASP.NET MVC3项目中,自定义404错误页面
在Web开发中,用户体验是至关重要的,一个友好的网站自然少不了自定义404错误页面. 让笔者为大家介绍404错误页面在ASP.NET MVC3项目中的配置: 第一步,在项目的Web.config文件中 ...
- VoiceEngine获取硬件信息
#include "webrtc\voice_engine\include\voe_base.h" #include "webrtc\voice_engine\inclu ...
- [原创]Hadoop-2.5.2-HA原文译
使用the Quorum Journal Manager实现HDFS高可用 2017/1/22 11:57:22 原文 目的(Purpose) * 这个指南提供了对HDFS-HA特性,使用QJM特性如 ...
- 列存储段消除(ColumnStore Segment Elimination)
列存储索引是好的!对于数据仓库和报表工作量,它们是真正的性能加速器.与聚集列存储结合,你会在常规行存储索引(聚集索引,非聚集索引)上获得巨大的压缩好处.而且创建聚集列存储索引非常简单: CREATE ...
- C++编程练习(3)----“实现简单的栈的顺序存储结构“
栈(stack)是限定仅在表尾进行插入和删除操作的线性表. 允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom). 栈又称为后进先出(Last In First Out)的线性表,简 ...
- HUST 1586 数字排列
1586 - 数字排列 时间限制:1秒 内存限制:128兆 91 次提交 36 次通过 题目描述 现有n个k位的数字,你的任务是重新安排数字每一位的位置,使得重新安排后这n个数字中最大的数字和最小的数 ...
- 【.Net Framework 体积大?】不安装.net framework 也能运行!?原理补充-3
继续补充点吧.接上一篇,我们实现了.net framework的精简的步骤. 有网友评论了,那个核心的 mscoree.dll 从.net framework 2.0开始,微软开始了新的CLR承载模型 ...
- [python]什么是生成器
看理论硬是看的抓狂,然后去百度贴吧看到了一句话,什么叫python生成器,简直秒懂