来自:Java中文社群

volatile 是并发编程的重要组成部分,也是面试常被问到的问题之一。不要向小强那样,因为一句:volatile 是轻量级的 synchronized,而与期望已久的大厂失之交臂。

volatile 有两大特性:保证内存的可见性和禁止指令重排序。那什么是可见性和指令重排呢?接下来我们一起来看。

内存可见性

要了解内存可见性先要从 Java 内存模型(JMM)说起,在 Java 中所有的共享变量都在主内存中,每个线程都有自己的工作内存,为了提高线程的运行速度,每个线程的工作内存都会把主内存中的共享变量拷贝一份进行缓存,以此来提高运行效率,内存布局如下图所示:

但这样就会产生一个新的问题,如果某个线程修改了共享变量的值,其他线程不知道此值被修改了,就会发生两个线程值不一致的情况,我们用代码来演示一下这个问题。

public class VolatileExample {// 可见性参数private static boolean flag = false;public static void main(String[] args) {new Thread(() -> {try {// 暂停 0.5s 执行Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}flag = true;System.out.println("flag 被修改成 true");}).start();// 一直循环检测 flag=truewhile (true) {if (flag) {System.out.println("检测到 flag 变为 true");break;}}}
}

以上程序的执行结果如下:

flag 被修改成 true

我们会发现永远等不到 检测到 flag 变为 true 的结果,这是因为非主线程更改了 flag=true,但主线程一直不知道此值发生了改变,这就是内存不可见的问题。

内存的可见性是指线程修改了变量的值之后,其他线程能立即知道此值发生了改变。

我们可以使用 volatile 来修饰 flag,就可以保证内存的可见性,代码如下:

public class VolatileExample {// 可见性参数private static volatile boolean flag = false;public static void main(String[] args) {new Thread(() -> {try {// 暂停 0.5s 执行Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}flag = true;System.out.println("flag 被修改成 true");}).start();// 一直循环检测 flag=truewhile (true) {if (flag) {System.out.println("检测到 flag 变为 true");break;}}}
}

以上程序的执行结果如下:

检测到 flag 变为 true flag 被修改成 true

指令重排

指令重排是指在执行程序时,编译器和处理器常常会对指令进行重排序,已到达提高程序性能的目的。比如小强要去图书馆还上次借的书,随便再借一本新书,而此时室友小王也想让小强帮他还一本书,未发生指令重排的做法是,小强先把自己的事情办完,再去办室友的事,这样显然比较浪费时间,还有一种做法是,他先把自己的书和小王的书一起还掉,再给自己借一本新书,这就是指令重排的意义。

但指令重排不能保证指令执行的顺序,这就会造成新的问题,如下代码所示:

public class VolatileExample {// 指令重排参数private static int a = 0, b = 0;private static int x = 0, y = 0;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < Integer.MAX_VALUE; i++) {Thread t1 = new Thread(() -> {// 有可能发生指令重排,先 x=b 再 a=1a = 1;x = b;});Thread t2 = new Thread(() -> {// 有可能发生指令重排,先 y=a 再 b=1b = 1;y = a;});t1.start();t2.start();t1.join();t2.join();System.out.println("第 " + i + "次,x=" + x + " | y=" + y);if (x == 0 && y == 0) {// 发生了指令重排break;}// 初始化变量a = 0;b = 0;x = 0;y = 0;}}
}

以上程序执行结果如下所示:可以看出执行到 48526 次时发生了指令重排,y 就变成了非正确值 0,显然这不是我们想要的结果,这个时候就可以使用 volatile 来禁止指令重排。

以上我们通过代码的方式演示了指令重排和内存可见性的问题,接下来我们用代码来演示一下 volatile 同步方式的问题。

volatile 非同步方式

首先,我们使用 volatile 修饰一个整数变量,再启动两个线程分别执行同样次数的 ++ 和 -- 操作,最后发现执行的结果竟然不是 0,代码如下:

public class VolatileExample {public static volatile int count = 0; // 计数器public static final int size = 100000; // 循环测试次数public static void main(String[] args) {// ++ 方式Thread thread = new Thread(() -> {for (int i = 1; i <= size; i++) {count++;}});thread.start();// -- 方式for (int i = 1; i <= size; i++) {count--;}// 等所有线程执行完成while (thread.isAlive()) {}System.out.println(count); // 打印结果}
}

以上程序执行结果如下:

1065

可以看出,执行结果并不是我们期望的结果 0,我们把以上代码使用 synchronized 改造一下:

public class VolatileExample {public static int count = 0; // 计数器public static final int size = 100000; // 循环测试次数public static void main(String[] args) {// ++ 方式Thread thread = new Thread(() -> {for (int i = 1; i <= size; i++) {synchronized (VolatileExample.class) {count++;}}});thread.start();// -- 方式for (int i = 1; i <= size; i++) {synchronized (VolatileExample.class) {count--;}}// 等所有线程执行完成while (thread.isAlive()) {}System.out.println(count); // 打印结果}
}

这次执行的结果变成了我们期望的值 0。

这说明 volatile 只是轻量级的线程可见方式,并不是轻量级的同步方式,所以并不能说 volatile 是轻量级的 synchronized,终于知道为什么面试官让我回去等通知了。

volatile 使用场景

既然 volatile 只能保证线程操作的可见方式,那它有什么用呢?volatile 在多读多写的情况下虽然一定会有问题,但如果是一写多读的话使用 volatile 就不会有任何问题。volatile 一写多读的经典使用示例就是 CopyOnWriteArrayList,CopyOnWriteArrayList 在操作的时候会把全部数据复制出来对写操作加锁,修改完之后再使用 setArray 方法把此数组赋值为更新后的值,使用 volatile 可以使读线程很快的告知到数组被修改,不会进行指令重排,操作完成后就可以对其他线程可见了,核心源码如下:

public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {private transient volatile Object[] array;final void setArray(Object[] a) {array = a;}  //...... 忽略其他代码
}

总结

本文我们通过代码的方式演示了 volatile 的两大特性,内存可见性和禁止指令重排,使用 ++ 和 -- 的方式演示了 volatile 并非轻量级的同步方式,以及 volatile 一写多读的经典使用案例 CopyOnWriteArrayList。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

因为我说:volatile 是轻量级的 synchronized,面试官让我回去等通知!相关推荐

  1. java volatile详解,互联网 面试官 如何面试

    写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家.扫码加微信好友进[程序员面试学习交流群],免费领取.也欢迎各位一起在群里探讨技术. 1. ...

  2. 一个volatile跟面试官扯了半个小时

    前言 volatile 应该算是Java 后端面试的必考题,因为多线程编程基本绕不开它,很适合作为并发编程的入门题. 开场 面试官:你先自我介绍一下吧! 安琪拉:   我是安琪拉,草丛三婊之一,最强中 ...

  3. 面试官最想要的synchronized,你值得拥有

    synchronized简介 synchronized 是Java语言的一个关键字,它本身的意思为同步,是用来保证线程安全的,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同 ...

  4. synchronized 面试五连击

    synchronized 面试五连击 目录 说一说自己对于 synchronized 关键字的了解 说说自己怎么使用 synchronized 关键字的,在项目中用到了吗 说一下 synchroniz ...

  5. 面试官最爱的 volatile 关键字,这些问题你都搞懂了没?

    前言 volatile相关的知识点,在面试过程中,属于基础问题,是必须要掌握的知识点,如果回答不上来会严重扣分的哦. volatile关键字基本介绍 volatile可以看成是synchronized ...

  6. 面试官:说一下Synchronized底层实现,锁升级的具体过程?

    介绍 这是我去年7,8月份面试的时候被问的一个面试题,说实话被问到这个问题还是很意外的,感觉这个东西没啥用啊,直到后面被问了一波new Object,Integer对象等作为加锁对象行吗?会出现哪些问 ...

  7. 【你好面试官】008 Java内存模型指volatile底层原理详解、多处理器原子操作实现原理

    微信公众号:你好面试官 这里没有碎片化的知识,只有完整的知识体系. 专注于系统全面的知识点讲解,面试题目解析; 如果你觉得文章对你有帮助,欢迎关注.分享.赞赏. ###前言 二蛋几天没有收到面试通知, ...

  8. 一个synchronized跟面试官扯了半个小时

    前言 话说上回HashMap跟面试官扯了半个小时之后,二面迎来了没有削弱前的钟馗,法师的钩子让安琪拉有点绝望.钟馗穿着有些微微泛黄的格子道袍,站在安琪拉对面,开始发难,其中让安琪拉印象非常深刻的是法师 ...

  9. 面试官真是搞笑!让实现线程安全的单例,又不让使用synchronized!

    单例模式,是Java中比较常见的一个设计模式,也是我在面试时经常会问到的一个问题. 经过我的初步统计,基本上有60%左右的人可以说出2-4种单例的实现方式,有40%左右的人可以说出5-6种单例的实现方 ...

最新文章

  1. 深刻理解Vue中的组件
  2. JS解决在提交form表单时某个值不存在 alter弹窗点确定不刷新界面
  3. AndroidStudio3.4.2 gradle5.1.1 apt注解解释器不执行
  4. 巧用shell+rsync服务实现日志自动过滤处理压缩并上传日志服务器,自动分类
  5. StringBuffer 和 StringBuilder 的 3 个区别!
  6. 【教程】超详细的虚拟无线控制器安装教程
  7. WindowsServer2012史记5-简洁,管理更多的服务器
  8. linux命令查询邮件发送状态,Linux发邮件之mail命令详解
  9. python合并两列数据_在pandas / python中的同一数据框中将两列合并...
  10. java string内部实现_Redis知识总结--string的内部实现
  11. python 3.7 安装 win32 win32com win32ras模块
  12. 如何在IDEA中操作数据库——导入驱动包
  13. 测开之路三十三:Flask实现扎金花游戏
  14. python中label组件参数_Tkinter:Label组件
  15. 李志敏 华中农业大学计算机学院,华中农业大学计算机教育论坛举行
  16. 模型矩阵、视图矩阵、投影矩阵
  17. Mave概念及其配置
  18. 2022年打工人转行实录!你后悔转行了吗?
  19. 巧妙利用DZ自有功能实现豆丁网在线文档功能
  20. Python之strip详解

热门文章

  1. HDU1874(Dijstra算法)
  2. poj3007(set的应用)
  3. 树莓派安装python3.5+tensorflow_树莓派4B安装Tensorflow的方法步骤
  4. 模板 - 质数筛法(线性筛法、二次筛法)
  5. c++引用matlab类,matlab调用C++函数浅谈(一)
  6. mysql innodb myisam 插入数据_mysql innodb换成myisam后插入数据变快?
  7. C - Catch That Cow POJ - 3278
  8. cpc无法获取系统office信息_智能云信息发布系统解锁信息获取新方式
  9. windows环境下Zookeeper的安装配置调试
  10. 定时登录下载sftp服务器上的某些有规则的文件