每日一问:谈谈 volatile 关键字
这是 wanAndroid 每日一问中的一道题,下面我们来尝试解答一下。
讲讲并发专题 volatile,synchronize,CAS,happens before, lost wake up
为了本系列的「短平快」,今天我们就来第一个主角:volatile。
保证内存可见性
前面我们讲到:Java 内存模型分为了主内存和工作内存两部分,其规定程序所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(赋值、读取等)都必须在工作内存中进行,而不能直接读取主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递都必须经过主内存的传递来完成。

这样就会存在一个情况,工作内存值改变后到主内存更新一定是需要一定时间的,所以可能会出现多个线程操作同一个变量的时候出现取到的值还是未更新前的值。
这样的情况我们通常称之为「可见性」,而我们加上 volatile 关键字修饰的变量就可以保证对所有线程的可见性。
这里的可见性是什么意思呢?当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。
为什么 volatile 关键字可以有这样的特性?这得益于 Java 语言的先行发生原则(happens-before)。简单地说,就是先执行的事件就应该先得到结果。
但是! volatile 并不能保证并发下的安全。
Java 里面的运算并非原子操作,比如 i++ 这样的代码,实际上,它包含了 3 个独立的操作:读取 i 的值,将值加 1,然后将计算结果返回给 i。这是一个「读取-修改-写入」的操作序列,并且其结果状态依赖于之前的状态,所以在多线程环境下存在问题。
要解决自增操作在多线程下线程不安全的问题,可以选择使用 Java 提供的原子类,如
AtomicInteger或者使用synchronized同步方法。原子性:在 Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量)才是原子操作。(变量之间的相互赋值不是原子操作,比如
y = x,实际上是先读取x的值,再把读取到的值赋值给y写入工作内存)
禁止指令重排
最开始看到「指令重排」这个词语的时候,我也是一脸懵逼。后面看了相关书籍才知道,处理器为了提高程序效率,可能对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
指令重排是一把双刃剑,虽然优化了程序的执行效率,但是在某些情况下,却会影响到多线程的执行结果。比如下面的代码:
boolean contextReady = false;
//在线程A中执行:
context = loadContext(); // 步骤 1
contextReady = true; // 步骤 2
//在线程B中执行:
while(!contextReady ){
sleep(200);
}
doAfterContextReady (context);
以上程序看似没有问题。线程 B 循环等待上下文 context 的加载,一旦 context 加载完成,contextReady == true 的时候,才执行 doAfterContextReady 方法。
但是,如果线程 A 执行的代码发生了指令重排,也就是上面的步骤 1 和步骤 2 调换了顺序,那线程 B 就会直接跳出循环,直接执行 doAfterContextReady() 方法导致出错。
而 volatile 采用「内存屏障」这样的 CPU 指令就解决这个问题,不让它指令重排。
使用场景
从上面的总结来看,我们非常容易得出 volatile 的使用场景:
- 运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
- 变量不需要与其他的状态变量共同参与不变约束。
比如下面的场景,就很适合使用 volatile 来控制并发,当 shutdown() 方法调用的时候,就能保证所有线程中执行的 work() 立即停下来。
volatile boolean shutdownRequest;
private void shutdown(){
shutdownRequest = true;
}
private void work(){
while (!shutdownRequest){
// do something
}
}
总结
说了这么多,其实对于 volatile 我们只需要知道,它主要特性:保证可见性、禁止指令重排、解决 long 和 double 的 8 字节赋值问题。
还有一个比较重要的是:它并不能保证并发安全,不要和 synchronize 混淆。
细心的你还会发现,在 Kotlin 语言中,其实是没有
volatile和synchronize这样的关键字的,那 Kotlin 是怎么处理并发问题的呢?感兴趣的一定要去看看。
文章参考:
漫画:什么是volatile关键字?(整合版)
《深入理解 Java 虚拟机》
每日一问:谈谈 volatile 关键字的更多相关文章
- Java面试官最爱问的volatile关键字
在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...
- Java面试官最常问的volatile关键字
在Java相关的职位面试中,很多Java面试官都喜欢考察应聘者对Java并发的了解程度,以volatile关键字为切入点,往往会问到底,Java内存模型(JMM)和Java并发编程的一些特点都会被牵扯 ...
- 面试必问的volatile关键字
原文: 卡巴拉的树 https://juejin.im/post/5a2b53b7f265da432a7b821c 在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度, ...
- 谈谈volatile关键字以及常见的误解
转载请保留以下声明 作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/9092520.html 近期看到C++标准中对volatile关键字的定义, ...
- JAVA多线程基础学习三:volatile关键字
Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...
- JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁
问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...
- JAVA多线程学习- 三:volatile关键字
Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...
- 【面试普通人VS高手系列】volatile关键字有什么用?它的实现原理是什么?
一个工作了6年的Java程序员,在阿里二面,被问到"volatile"关键字. 然后,就没有然后了- 同样,另外一个去美团面试的工作4年的小伙伴,也被"volatile关 ...
- 围绕一个 volatile 关键字居然可以问出来 16 个问题
对于 Java 每次面试就会想到多线程,多线程问题基本跑不了要问一下 volalite 关键字,可是我万万没想到居然一个 volatile 关键字可以连续问题出来 16 个问题!看下你能回答出来几个? ...
随机推荐
- windows下PyTorch安装之路记录
最近两天被windows下pytorch的安装给搞得很烦了,不过在今天终于安装成功了,如下图所示 下面详细说下此次安装的详细记录吧.我的电脑环境是Windows10+cuda9.0+cudnn7.1. ...
- mybatis分页的一种解决方案
mybatis自定义分页解决方案 1.PageSqlProvider<T> —— 提供默认的分页列表查询 package com.xinyartech.erp.core.base; im ...
- 深入V8引擎-初始化之InitPlatform
上一篇其实想讲初始化的第二步,但是内容比较无聊,所以换了一个话题,谈了谈v8的命名空间和宏,稍微轻松一下. 在这里还是接着说说初始化过程,毕竟写博客的初衷是对自己努力的记录,不是为了吸粉,这篇没图,对 ...
- vue-如何实现带参数跳转页面
[前后端分离项目之vue框架经验总结] 文/朱季谦 在vue框架的前端页面上,若要实现页面之间的带参数跳转,可参考以下实现过程: 例如,点击截图中的“查看试卷”,可实现带参跳转到相应的试卷页面,该功能 ...
- WPF 精修篇 获取系统颜色和字体样式
原文:WPF 精修篇 获取系统颜色和字体样式 看效果 <Grid> <Rectangle Fill="{DynamicResource {x:Static SystemCo ...
- Git分支和版本回退
一.分支 1.分支简单介绍 简单使用: 可以将git branch new_branch和git checkout new_branch两个命令合并成一个命令: git checkout -b new ...
- python机器学习---线性回归案例和KNN机器学习案例
散点图和KNN预测 一丶案例引入 # 城市气候与海洋的关系研究 # 导包 import numpy as np import pandas as pd from pandas import Serie ...
- Node.js返回JSON
在使用JQuery的Ajax从服务器请求数据或者向服务器发送数据时常常会遇到跨域无法请求的错误,常用的解决办法就是在Ajax中使用JSONP.基于安全性考虑,浏览器会存在同源策略,然而<scri ...
- 扒一扒那些年我们console过的那些事儿
今天无意间打开百度发现,百度里面有下面这个东西,煞是好奇,原来console.log()不只是我们常用的那个console.log(). console.log("%c百度2019校园招聘简 ...
- 【转载】Gradle学习 第七章:Java快速入门
转载地址:http://ask.android-studio.org/?/article/22 7.1. The Java plugin(Java插件) As we have seen, Gradle ...