在 Java 中,volatile 是一种关键字,用于修饰变量。使用 volatile 关键字修饰的变量具有可见性和有序性,但不保证原子性。
相关定义说明
原子性(Atomicity):即一个操作或者多个操作,要么全部执行,并且执行的过程不会被任何因素打断,要么都不执行。
有序性(Ordering):指指令在执行过程中的顺序,一个操作执行在另一个操作之前或者在其执行之后。即程序执行的顺序按照代码的先后顺序执行。
可见性(Visibility):指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
volatile 实现原理
volatile 关键字在底层的实现主要是通过内存屏障(memory barrier)来实现的。内存屏障是一种 CPU 指令,用于强制执行 CPU 的内部缓存与主内存之间的数据同步。
在 Java 中,当线程读取一个 volatile 变量时,会从主内存中读取变量的最新值,并把它存储到线程的工作内存中。当线程写入一个 volatile 变量时,会把变量的值写入到线程的工作内存中,并强制将这个值刷新到主内存中。这样就保证了 volatile 变量的可见性和有序性。
在 Java 5 之后,volatile 的实现还引入了“内存屏障插入”的机制,内存屏障插入是指在指令序列中插入内存屏障以保证变量的可见性和有序性。
主内存和工作内存
Java 内存模型规定,所有的变量(实例变量和静态变量)都必须存储在主内存中,每个线程也会有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量,如下图所示: 这样设计的目的主要是为了提升程序的并发性能以及多线程之间的可见性问题。
主内存是 Java 虚拟机中的一块共享内存,所有线程都可以访问。而每个线程还有自己的工作内存,线程的工作内存中存储了主内存中的变量副本的拷贝。这样做的好处是,线程之间不需要同步所有变量的读写操作,只需要同步主内存中的变量即可,这样可以提高程序的执行效率。同时,由于每个线程都有自己的工作内存,因此线程之间的变量操作互相不影响,从而提高了程序的并发性能。
内存屏障
内存屏障是一种硬件机制,用于控制 CPU 缓存和主内存之间的数据同步。在 Java 中,内存屏障通常有两种:读屏障和写屏障。
在有内存屏障的地方,会禁止指令重排序,即屏障下面的代码不能跟屏障上面的代码交换执行顺序。在有内存屏障的地方,线程修改完共享变量以后会马上把该变量从本地内存写回到主内存,并且让其他线程本地内存中该变量副本失效(使用 MESI 协议)。
MESI 协议是一种缓存一致性协议,它是支持写回(write-back)缓存的最常用协议。MESI 协议基于总线嗅探机制实现了事务串形化,也用状态机机制降低了总线带宽压力,做到了 CPU 缓存一致性。MESI 协议这 4 个字母代表 4 个状态,分别是:Modified(已修改)、Exclusive(独占)、Shared(共享)、Invalidated(已失效)。
特殊说明
以上内容来自我的《Java 面试突击训练营》,这门课程是有着十几年工作经验(前 360 开发工程师),10 年面试官经验的我,花费 4 年时间打磨完成的一门视频面试课。学完训练营的课程之后,基本可以应对目前市面上绝大部分公司的面试了,并且课程配备了 9 大就业服务,帮助上千人找到 Java 工作,其中上百人拿到大厂 Offer,学员最高薪资 70W 年薪,面试课目录和 9 大服务如下:
加我微信咨询:vipStone【备注:训练营】