这是 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 的使用场景:

  1. 运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
  2. 变量不需要与其他的状态变量共同参与不变约束。

比如下面的场景,就很适合使用 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 关键字的更多相关文章

  1. Java面试官最爱问的volatile关键字

    在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...

  2. Java面试官最常问的volatile关键字

    在Java相关的职位面试中,很多Java面试官都喜欢考察应聘者对Java并发的了解程度,以volatile关键字为切入点,往往会问到底,Java内存模型(JMM)和Java并发编程的一些特点都会被牵扯 ...

  3. 面试必问的volatile关键字

    原文: 卡巴拉的树   https://juejin.im/post/5a2b53b7f265da432a7b821c 在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度, ...

  4. 谈谈volatile关键字以及常见的误解

    转载请保留以下声明 作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/9092520.html 近期看到C++标准中对volatile关键字的定义, ...

  5. JAVA多线程基础学习三:volatile关键字

    Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...

  6. JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁

    问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...

  7. JAVA多线程学习- 三:volatile关键字

    Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...

  8. 【面试普通人VS高手系列】volatile关键字有什么用?它的实现原理是什么?

    一个工作了6年的Java程序员,在阿里二面,被问到"volatile"关键字. 然后,就没有然后了- 同样,另外一个去美团面试的工作4年的小伙伴,也被"volatile关 ...

  9. 围绕一个 volatile 关键字居然可以问出来 16 个问题

    对于 Java 每次面试就会想到多线程,多线程问题基本跑不了要问一下 volalite 关键字,可是我万万没想到居然一个 volatile 关键字可以连续问题出来 16 个问题!看下你能回答出来几个? ...

随机推荐

  1. Dart:3.Dart运算符、流程控制

    一 . 运算符 以下列出 Dart 的运算符,从高到低按照优先级排列: 描述 运算符 一元运算符(后置) expr++ expr-- () [] . ?. 一元运算符(前置) -expr !expr ...

  2. 使用Jenkins来实现内部的持续集成流程(下)

    目录 配置项目构建 添加任务 添加源代码地址和登录凭据 添加构建触发器  TFS添加WebHook  添加构建步骤 后端UI  API端  配置项目构建 1.添加任务 2.添加源代码地址和登录凭据 添 ...

  3. 《 .NET并发编程实战》阅读指南 - 第5章

    先发表生成URL以印在书里面.等书籍正式出版销售后会公开内容.

  4. [转] vue父组件触发子组件事件

    1. 父组件中获取子组件方法 $children 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template>     < ...

  5. thread stack size not set; configure via D:\Program Files\elasticsearch-5.0.0\config\jvm.options or ES_JAVA_OPTS

    抄自:http://blog.csdn.net/leo063/article/details/52994786 thread stack size not set; configure via D:\ ...

  6. WMware Workstation Pro安装教程

    [1]右键单击WMware Workstation Pro的可执行文件(.exe),选择以管理员身份运行 [2]点击下一步 [3]勾选我接受许可协议中的条款,点击下一步 [4]点击更改,选择安装位置( ...

  7. Delphi - 采用第三方控件TMS、SPComm开发串口调试助手

    第三方控件TMS.SPComm的下载与安装 盒子上可搜索关键字进行下载,TMS是.dpk文件,SPComm.pas文件: 安装方法自行百度,不做赘述. 通过TMS控件进行界面布局 界面预览: Delp ...

  8. Python学习笔记之replace()

    10-2 可使用方法replace()将字符串中的特定单词都替换为另一个单词. 读取你刚创建的文件learning_python.txt 中的每一行,将其中的Python 都替换为另一门语言的名称,如 ...

  9. HighChat 动态绑定数据记录

    最近刚开始做图形操作,纠结了一上午,highchat 动态绑定数据这块一直不知道怎么绑定,后来多次尝试,发现 1.x轴的数据是个数组格式,我从后台传到前台的时候,js中用数组进行处理数据,然后赋值到c ...

  10. 左右对齐Justify遇到的坑

    遇到的问题 这两天在开发一个病历的对外展示页面,设计稿上label是左右拉伸对齐的,显示效果如下: 怎么实现这种效果呢? 首先想到的是文字中间加空格,但是这种方式太low了,而且不太容易控制.网上查资 ...