写点什么

面试时通过 volatile 关键字,全面展示线程内存模型的能力

  • 2021 年 11 月 12 日
  • 本文字数:1732 字

    阅读完需:约 6 分钟

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 的


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


优势了。


请记住这个结论,如果某个变量在多线程环境下只有读或者是只有写的操作,建议把它设置成 volatile,这样能提升多线程并发时的效率。


4 如果可以,再扩展到 ConcurrentHashMap 的底层代码


说好上述内容以后,其实大家已经可以能充分展示内存方面的技能了,不过大家还可以多说一句:我还看过 ConcurrentHashMap 的底层源码,其中用到了 volatile 关键字。


ConcurrentHashMap 是支持并发的 HashMap,说白了就当多个线程同时读写 ConcurrentHashMap 对象时,不会有问题。

评论

发布
暂无评论
面试时通过volatile关键字,全面展示线程内存模型的能力