volatile修饰的变量_从底层原理深度剖析volatile关键字,彻底征服面试官

本篇文章从底层原理层面深度剖析volatile关键字是如何实现内存可见性的,同时引入了Java内存模型、指令重排序以及内存屏障等知识点作为原理分析的知识支撑。

阅读本文之前,推荐大家先阅读作者之前的一篇关于happens-before的文章,这样更有助于大家对volatile关键字底层原理的理解。

面试官为什么总是问happens-before规则,看完这篇文章你就懂了

简述Java内存模型

Java内存模型分为主内存和线程工作内存两大类。

  • 主内存: 多个线程共享的内存。如下图所示,方法区和堆属于主内存区域。
  • 线程工作内存: 每个线程独享的内存。如下图所示,虚拟机栈、本地方法栈、程序计数器属于线程独享的工作内存。
6bdaabf71c288edaf2846904e04d43ef.png

Java内存模型

Java内存模型规定:所有变量都需要存储在主内存中,线程工作内存保存了变量在主内存中的副本,线程对变量的所有操作都在工作内存中进行,执行结束后在同步到主内存中去。这里必然会存在时间差,在这个时间差内,该线程对副本的操作,对于其他线程是不见的,从而造成了可见性问题。

2c6329124f9d4e3bc169ffdee1acefe9.png

Java线程内存模型图

指令重排序

JVM对代码进行编译优化,导致代码可能并不是按照代码编写顺序执行,而是按照JVM进行编译优化后的顺序执行。指令重排序对并发编程安全性有很大影响,所以提供了一些happens-before规则定义一些禁止编译优化的场景。具体可见文章:面试官为什么总是问happens-before规则,看完这篇文章你就懂了

volatile的作用

保证共享变量的可见性: 使用volatile修饰的变量,任何线程对其进行操作都是在主内存中进行的,不会产生副本,从而保证共享变量的可见性。 防止局部指令重排序: happens-before规则中的 volatile变量规则规定了一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。

volatile如何防止指令重排序

volatile是通过内存屏障来防止指令重排序的

硬件层面的内存屏障分为Load Barrier 和 Store Barrier即读屏障和写屏障

  • 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据。
  • 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

Java内存屏障类型把上述两种内存屏障两两组合,如下图所示:

61995e1c0d2f001b149edf7459668cb7.png

Java内存屏障类型

volatile防止指令重排序具体步骤:

  1. 在每个volatile写操作的前面插入一个StoreStore屏障。
  2. 在每个volatile写操作的后面插入一个StoreLoad屏障。
  3. 在每个volatile读操作的后面插入一个LoadLoad屏障。
  4. 在每个volatile读操作的后面插入一个LoadStore屏障。
1bf9cc986913bbcb102e195df836b1b2.png

volatile写内存屏障示意图

3addfe2cfdef5981e49aeb536af3864d.png

volatile读内存屏障示意图

volatile总结

volatile解决的是多线程共享变量可见性问题,但是被volatile修饰的变量操作并非具有原子性。如下面代码所示:

private static volatile int count = 0;public static void main(String[] args) {    Thread thread1 = new Thread(()->{        for (int i=0; i<1000; i++){            count++;        }    });    Thread thread2 = new Thread(()->{        for (int i=0; i<1000; i++){            count++;        }    });    thread1.start();    thread2.start();    while (thread1.isAlive() && thread2.isAlive()){}    System.out.println("count结果为:" + count);}
fad28cbdb208356fe4e8172f840287e7.png

源码截图-方便手机端用户阅读

上述代码两个线程同时执行count++操作1000次,多次执行结果均不为2000,可见被volatile修饰的变量操作不具有原子性。注:可以通过对count++加锁的方式或使用AtomicLong和LongAdder(JDK8推荐使用)类来实现count++的原子性。

END

笔者是一位热爱互联网、热爱互联网技术、热于分享的年轻人,如果您跟我一样,我愿意成为您的朋友,分享每一个有价值的知识给您。喜欢作者的同学,点赞+转发+关注哦!

点赞+转发+关注,私信作者“读书笔记”即可获得BAT大厂面试资料、高级架构师VIP视频课程等高质量技术资料。

bd8be2b1ebb4e2f31924180da1f5614a.png

BAT等一线互联网面试资料和VIP高级架构师视频

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值