面试时通过volatile关键字,全面展示线程内存模型的能力
面试时,面试官经常会通过volatile关键字来考核候选人在多线程方面的能力,一旦被问题此类问题,大家可以通过如下的步骤全面这方面的能力。
1 首先通过内存模型说明volatile关键字的作用
先说明,用volatile修饰的变量,能直接修改内存内容,修改后的变量对其他线程是可见的。然后展开说明如下的内容。
多线程并发操作同一资源时,可能会出现最终结果和预期不同的情况,刚才我们也已经通过线程安全和不安全相关的案例,直观地看到了这一情况,这里我们将通过线程的内存结构来详细分析下造成“最终结果不一致”的原因。
如果某个线程要操作data变量,该线程会先把data变量装载到线程内部的内存中做个副本,之后线程就不再和在主内存中的data变量有任何关系,而是会操作副本变量的值,操作完成后,再把这个副本回写到主内存(也就是堆内存)中,这个过程如下图所示。

假设data的初始值是0,有100个线程并发地对它进行加1操作,预期的运行结果是100。但在实际的操作过程中,假设A线程和B线程并发地data,其中A读到的值是0,B读到的是1。当B在它的线程内部内存中完成加1操作(data变成2),会把data回写到主内存里,这时主内存里的data也是2。
但之后,A线程也完成了加1操作(此时A内部线程中的data副本是1),在之后的回写过程中,会把主内存中的data变量从2设置成1,这样就造成数据不一致的问题了。
但是,如果data变量被volatile变量修饰,那么A线程修改好的data变量,无需等到“”回写“”阶段,能直接写回到主内存里,这就能导致该变量对其它线程“立即可见”。
2 同时说明,volatile不能解决数据不一致的问题
如果某个变量之前加了volatile,线程在每次使用该变量时,都会从主内存中读取该变量最新的值,而且,某线程一旦修改了该变量,这个修改会立即回写到主内存里。
既然是在操作前会从主内存中读取变量最新的值,而且每次修改后都会立即回写到主内存,这样的话是否能解决多线程中数据不一致的问题呢?通过下面的VolilateDemo.java代码,我们来看下这个问题的答案。
1 public class VolilateDemo extends Thread {
2 //启动1000个线程,对这个被volatile修饰的变量进行加1操作
3 public static volatile int cnt = 0;
4 public static void add() {
5 // 延迟1毫秒,增加多线程并发抢占的概率
6 try { Thread.sleep(1);}
7 catch (InterruptedException e) { }
8 cnt++;//加操作
9 }
10 public static void main(String[] args) {
11 // 同时启动1000个线程,去进行加操作
12 for (int i = 0; i < 1000; i++) {
13 new Thread(new Runnable() {
14 public void run()
15 {VolilateDemo.add(); }
16 }).start();
17 }
18 System.out.println("Result is " + VolilateDemo.cnt);
19 }
20 }
在main函数的第12行里,通过for循环启动1000个线程。从第13到16行里,我们通过了Runnable类定义了线程的动作,每个线程启动后,会调用第15行的add方法对用volatile修饰的cnt变量进行加1操作。
多次运行的结果可能不一样,但在大多数情况下,最终cnt的值会小于1000,也就是说,用volatile修饰的变量不能保证数据一致性,换句话说,volatile不能当锁来用,因为它不能保证主内存的变量在同一时间段里只被一个线程操作。
3 然后说下volatile的作用
那么volatile有什么用呢?被volatile修饰的变量每次在使用时,不是从各线程的内部内存中拿,而是从主内存中拿。这样就能避免“创建副本”到“把副本回写到主内存中”等的操作,从而能提升效率。
但请注意,如果我们在多线程环境下,针对某个变量有读和写的操作,那么别把它修饰成volatile,因为为了解决数据不一致的问题,我们会给该变量加锁,这样该变量在一个时间段里只会有一个线程进行操作,这样就无法发挥出volatile的优势了。
请记住这个结论,如果某个变量在多线程环境下只有读或者是只有写的操作,建议把它设置成volatile,这样能提升多线程并发时的效率。
4 如果可以,再扩展到ConcurrentHashMap的底层代码
说好上述内容以后,其实大家已经可以能充分展示内存方面的技能了,不过大家还可以多说一句:我还看过ConcurrentHashMap的底层源码,其中用到了volatile关键字。
ConcurrentHashMap是支持并发的HashMap,说白了就当多个线程同时读写ConcurrentHashMap对象时,不会有问题。
该对象存储键值对的Node对象定义如下,其中表示值的val变量被volatile修饰,也就是说,A线程对该ConcurrentHashMap的操作,能立即回写到主内存,所以其它线程也能立即可见,所以能支持并发。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
//可以看到这些都用了volatile修饰
volatile V val;
volatile Node<K,V> next;
省略其它代码
}
当大家从volatile关键字引申到ConcurrentHashmap底层源码后,面试官就会认识你很资深。我记得当初,我去面试一家比较大的互联网公司,就这样说了一通,然后就直接通过这轮技术面试了(不过还有后继部门经理的技术面试)。
请大家关注我的公众号:一起进步,一起挣钱,在本公众号里,会有很多精彩的面试文章。

面试时通过volatile关键字,全面展示线程内存模型的能力的更多相关文章
- 面试中的volatile关键字
在Java的面试当中,面试官最爱问的就是volatile关键字相关的内容.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...
- 面试官:volatile关键字用过吧?说一下作用和实现吧
volatile 可见性的本质类似于CPU的缓存一致性问题,线程内部的副本类似于告诉缓存区 面试官:volatile关键字用过吧?说一下作用和实现吧 https://blog.csdn.net/ ...
- java线程内存模型,线程、工作内存、主内存
转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程 ...
- Java线程内存模型-JVM-底层原理
public class Demo1 { private static boolean initFlag=false; public static void main(String[] args) t ...
- Volatile关键字回顾之线程可见性
java中,volatile关键字有两大作用: 1.保证线程的可见性 2.防止指令重排序 这篇文章主要通过典型案例,体现可见性这一特性. 概念: java中,堆内存是线程共享的.而每个线程,都应该有自 ...
- 【Java并发】1. Java线程内存模型JMM及volatile相关知识
Java招聘知识合集:https://www.cnblogs.com/spzmmd/tag/Java招聘知识合集/ 该系列用于汇集Java招聘需要的知识点 JMM 并发编程的三大特性:可见性(vola ...
- 牛客网Java刷题知识点之什么是单例模式?解决了什么问题?饿汉式单例(开发时常用)、懒汉式单例(面试时常用)、单例设计模式的内存图解
不多说,直接上干货! 什么是单例设计模式? 解决的问题:可以保证一个类在内存中的对象唯一性,必须对于多个程序使用同一个配置信息对象时,就需要保证该对象的唯一性. 如何保证? 1.不允许其他程序用new ...
- JDK1.8-Java虚拟机运行时数据区域和HotSpot虚拟机的内存模型
目录 介绍 官方文档规定的运行时数据区域 程序计数器 Java虚拟机栈 本地方法栈 虚拟机栈和本地方法栈溢出 Java堆 演示堆内存溢出 方法区 运行时常量池 演示方法区溢出 HotSpot虚拟机的内 ...
- java核心技术-多线程之线程内存模型
对于每一种编程语言,理解它的内存模型是理所当然的重要.下面我们从jvm的内存模型来体会下java(不限java语言,严格来讲是JVM内存模型,所有JVM体系的变成语言均适用)的内存模型. 堆: 就是我 ...
随机推荐
- [日常摸鱼]bzoj1036 [ZJOI2008]树的统计Count
听说后天会考x 省选居然还考模板题的么(好吧好像NOI也有考而且也是树剖-) 题意:一棵树,每个点有权值,三种操作:单点修改.求链上最大值.求链上权值和. 直接上模板. 我可能不会写单点修改的线段树了 ...
- 我用 go-zero 一周实现了一个中台系统,已开源!
作者:Jack 最近发现golang社区里出了一个新星的微服务框架,来自好未来,光看这个名字,就很有奔头,之前,也只是玩过go-micro,其实真正的还没有在项目中运用过,只是觉得 微服务,grpc ...
- 性能监控工具nmon及nmon_analyser的使用
nmon和nmon_analyser下载地址: http://nmon.sourceforge.net/pmwiki.php?n=Site.Download 使用步骤: 1.nmon 根据系统版本 ...
- Protobuf简单类型直接反序列化方法
我有一个想法,有一个能够进行跨平台的高性能数据协议规范,能够让数据在两个不同的程序之间进行读取,最好能够支持直接将object序列化,那就完美了. 目标 支持任意Object序列化 支持从类似Syst ...
- 【命令】top命令
功用: <---实时动态地查看系统的整体运行情况,两个命令都是查看运行状况,但是htop更加易用---> TOP是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命 ...
- Windows权限维持
前言 最近终于不是那么忙了,有时间静下心来学点知识,这篇文章自起稿到发布,用时近三周,其中有近一周的时间在迷茫在焦躁,甚至怀疑.否定自己.网上的表哥们个顶个儿的优秀,于是就给自己很大的压力,所以那一个 ...
- cmd运行乱码或显示编码GBK的不可映射字符解决方法
出现这样的错误,一般是因为代码中含有中文字符,注释中的中文字符也算.由于使用cmd运行java程序的时候,系统默认的编码格式是gbk.而包含中文字符的代码一般是Unicode格式,所以直接运行含有中文 ...
- [LeetCode98]98. Validate Binary Search Tree判断二叉搜索树
判断二叉搜索树的方法是: 中序遍历形成递增序列 //全局变量记录中序遍历产生的序列,因为要递归,所以要用全局变量 List<Integer> list = new ArrayList< ...
- 谈谈 Vue 模板和 JSX
工具链 从学习曲线角度来讲,结合我个人体会,React 学习路线是比 Vue 陡峭的,这个和 JSX.Template 有关吗?当然有.在 React 中使用 JSX,众所周知, JSX 需要经过 T ...
- 使用纯 CSS 实现滚动阴影效果
开门见山,有这样一种非常常见的情况,对于一些可滚动的元素而言.通常在滚动的时候会给垂直于滚动的一侧添加一个阴影,用于表明当前有元素被滚动给该滚出了可视区域,类似这样: 可以看到,在滚动的过程中,会出现 ...