目录
volatile的作用
- 保证变量在多线程中的可见性
- 保证指令的有序性(禁止指令重排)
保证变量在多线程中的可见性
-
主存中有一个变量a, 它被volatile关键字所修饰, 并且它的值是0
-
T1和T2线程读取了变量a, 此时它们的线程的本地内存(CPU寄存器或高速缓存)中的值都是0
-
如果T1修改了变量a的值为1, 由于被a变量被volatile所修饰, 那么T1修改之后的a就会被刷新到主存(写屏障), 并且执行T2线程的CPU对a变量的缓存行会失效(读屏障), 强制从主存中获取值, 到达了多线程中变量的可见性的目的
-
通过内存屏障保证变量在多线程中的可见性, 如果字段被volatile修饰:
- 读屏障: 在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
- 写屏障: 在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存
保证指令的有序性(禁止指令重排)
指令重排的意义
-
JVM能够根据CPU的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥CPU的性能,提高效率。
-
单线程情况下, 指令重排没问题, 但是多线程环境下, 指令重排可能造成程序的执行结果并不是我们所期待的, 比如单例模式的DCL写法对DCL单例模式的思考
volatile通过两点保证指令有序性
happens-before
happens-before原则(定义一些禁止编译优化的场景,保证并发编程的正确性), 下图来源于: 来源《Java并发编程实践》
内存屏障
-
编译器在生成字节码时, 会在指令序列中插入内存屏障,会多出一个 lock 前缀指令
-
内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题,保证指令重排序后与之前的输出结果一 样,使性能得到优化, 处理器在进行重排序时是会考虑指令之间的数据依赖性。
-
先于这个内存屏障的指令必须先执行,后于这个内存屏障 的指令必须后执行
使用 volatile 的经典场景
- 双重校验锁 DCL(double checked locking)
- ConcurrentHashMap的哈希数组Node[]
- 原子类例如AtomicInteger的value属性
- 多个线程操作同一块内存的同一个变量(保证有序性和内存可见性,有序性和可见性同等重要,都不能忽略)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2019-03-28 docker-compose学习