这是 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 语言中,其实是没有 volatilesynchronize 这样的关键字的,那 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. 大数据基础总结---HDFS分布式文件系统

    HDFS分布式文件系统 文件系统的基本概述 文件系统定义:文件系统是一种存储和组织计算机数据的方法,它使得对其访问和查找变得容易. 文件名:在文件系统中,文件名是用于定位存储位置. 元数据(Metad ...

  2. golang --os系统包

    环境变量 Environ 获取所有环境变量, 返回变量列表 func Environ() []string package main import ( "fmt" "os ...

  3. 关于CSS特性和选择器

    1层叠  样式冲突的遵循就近原则, 2继承  子代会继承父代的某些属性,例如字体大小,字体颜色 3优先级 !important 权重最高 通配符 * 和 继承的属性 权重为 0 0 0 0 行内样式权 ...

  4. ionic4 页面跳转传值和新页面取值

    页面跳转 : <ion-row *ngFor="let item of aboutData.stockData" [routerLink]="[ '/stock-d ...

  5. Vue项目运行或打包时,频繁内存溢出情况CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

    前端使用基于vue的Nuxt框架,但是随着项目功能增多,项目变大,频繁出现此种情况,原因是项目太大,导致内存溢出,排除代码问题外,可参照以下方式解决 解决方案 1.全局安装increase-memor ...

  6. web的应用模式

    在开发web应用中,有两种模式: 1.前后端分离. 2.前后端不分离. 一.前后端不分离 在前后端不分离的应用模式中,前端页面看到的效果是有后端控制的,由后端渲染页面或重定向,也就是后端需要控制前端的 ...

  7. 【转】Git使用教程之基础篇

    Git使用教程 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是 ...

  8. App过大

    最近开发中遇到一个报错信息 如下 Error:Cannot fit requested classes in a single dex file.Try supplying a main-dex li ...

  9. logger(三)log4j2简介及其实现原理

    一.log4j2简介 log4j2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步.等等),使得日志的吞吐量.性能比log4j 1.x提高10倍,并解决了一些死锁的bug, ...

  10. logger(二)logback简介及其实现原理

    一.logback简介 logback是log4j创始人写的,性能比log4j要好,目前主要分为3个模块 logback-core:核心代码模块 logback-classic:log4j的一个改良版 ...