“在代码世界里,你遇到过多少次‘似懂非懂’的困境?我是曾续缘,一个喜欢把技术嚼碎了讲给你的开发者。点击关注,从此每个技术卡点都有解!”
volatile
的特性
volatile
关键字在Java中用于声明变量,以确保对变量的读写操作对所有线程立即可见,并且防止指令重排序。
volatile
通常用于简单的标志位或者状态变量,而不适合用于复杂的逻辑操作,因为它不能保证复合操作的原子性。
volatile
的主要特性包括:
可见性(Visibility):
- 当一个线程修改了一个
volatile
变量的值时,这个新值会立即被写入主内存(Main Memory),从而对其他线程可见。 - 其他线程读取
volatile
变量时,会从主内存中读取最新值,而不是使用工作内存中的缓存值。
有序性(Ordering):
volatile
变量的读写操作前后会插入特定的内存屏障(Memory Barrier),这些内存屏障会禁止处理器对volatile
变量相关的指令进行重排序。- 这意味着在
volatile
变量写操作之后的操作不会被重排序到写操作之前,在读操作之前的操作不会被重排序到读操作之后。
字节码体现
在Java中,当使用volatile
关键字修饰一个变量时,这个关键字在编译后的字节码中会有特殊的体现。volatile
关键字在字节码层面主要通过ACC_VOLATILE
标志来表示。
当一个变量被声明为volatile
时,Java编译器会在编译生成的字节码中为这个变量设置ACC_VOLATILE
访问标志。这个标志会告诉Java虚拟机(JVM)这个变量是volatile
的,因此JVM在执行时会遵循volatile
的内存语义。
在字节码中,volatile
变量的读写操作会被翻译为相应的字节码指令,比如getfield
和putfield
。这些指令在执行时,JVM会确保volatile
变量的内存语义得到遵守,比如在写入volatile
变量后立即刷新到主内存,以及在读取volatile
变量前从主内存中重新加载最新值。
JVM体现
在JVM(Java虚拟机)层面,volatile
关键字的实现涉及到JVM如何处理volatile
变量的读写操作,以及如何保证volatile
的内存语义。JVM层面的实现主要包括以下几个方面:
- 内存屏障(Memory Barriers):
- JVM在执行到
volatile
变量的读写操作时,会插入特定的内存屏障来保证volatile
的内存语义。 - 对于
volatile
变量的写操作,JVM会插入一个写屏障(Write Barrier),确保写操作之前的所有内存操作都已完成并刷新到主内存中,在此之前的数据不会因为处理器优化而延迟写入主内存。 - 对于
volatile
变量的读操作,JVM会插入一个读屏障(Read Barrier),确保读操作之后的所有内存操作都必须等待读操作完成,并且从主内存中读取最新值。
- JVM在执行到
volatile
变量的状态管理:- JVM会为每个
volatile
变量维护一个状态,用于跟踪该变量的最新值和是否被修改过。 - 当一个线程修改了
volatile
变量时,JVM会更新这个状态,并确保其他线程在读取该变量时能够看到最新的状态。
- JVM会为每个
- 指令重排序的禁止:
- JVM会确保
volatile
变量的读写操作不会被重排序到其他内存操作之前或之后,从而保证了代码的执行顺序与程序代码中的顺序一致。
- JVM会确保
系统底层体现
在系统底层,即硬件层面,volatile
关键字的实现依赖于处理器和内存系统的特性。volatile
的关键特性是保证对所有线程的可见性和有序性,这在系统底层主要通过以下机制实现:
缓存一致性协议(Cache Coherence Protocols):
- 在多处理器系统中,每个处理器都有自己的缓存。当一个处理器修改了一个内存地址的值时,它会通过缓存一致性协议(如MESI协议)通知其他处理器,使得其他处理器缓存中的对应数据无效,需要时必须从主内存重新读取。
volatile
变量的写操作会触发缓存一致性协议,确保新值对所有处理器可见。
处理器级的内存屏障(Processor-Level Memory Barriers):
- 处理器提供了一些指令来禁止特定的类型重排序。这些指令被称为内存屏障,它们可以确保某些内存操作执行的顺序。
volatile
变量的读写操作前后会插入特定的内存屏障,这些屏障会禁止处理器对volatile
变量相关的指令进行重排序。
总线锁(Bus Locks):
- 在某些情况下,处理器可能会通过总线锁来禁止其他处理器修改同一个内存地址的值,从而确保对
volatile
变量的写操作的原子性。
缓存一致性协议
缓存一致性协议是多处理器系统中确保缓存一致性的机制,它确保所有处理器都能够看到主内存中的最新数据。缓存一致性协议主要有几种,如MESI(Modified, Exclusive, Shared, Invalid)、MSI(Modified, Shared, Invalid)、MOSI(Modified, Owned, Shared, Invalid)等。
以MESI协议为例,它定义了缓存行(Cache Line)的四种状态:
- Modified(修改):缓存行包含的数据与主内存中的数据不一致,并且这个缓存行已经被修改。当处理器想要写入主内存时,它必须先将其修改为Modified状态。
- Exclusive(独占):缓存行包含的数据与主内存中的数据一致,并且这个缓存行只存在于一个缓存中。当处理器读取主内存中的数据时,它将数据加载到缓存中,并将其状态设置为Exclusive。
- Shared(共享):缓存行包含的数据与主内存中的数据一致,并且这个缓存行存在于多个缓存中。当多个处理器读取同一内存地址的数据时,它们可能会将其缓存行设置为Shared状态。
- Invalid(无效):缓存行包含的数据无效,或者已经被修改但还没有同步回主内存。
缓存一致性协议的工作原理如下:
- 当一个处理器修改了一个内存地址的值时,它必须将对应的缓存行状态从Shared或Exclusive变为Modified。这个操作会触发缓存一致性协议。
- 其他处理器如果拥有这个缓存行的Shared或Exclusive状态,它们必须将这个缓存行设置为Invalid状态,并且从主内存中重新读取数据。
- 当处理器想要写入主内存时,它必须将其修改的缓存行状态从Modified变为Invalid,并将数据写入主内存。
- 其他处理器如果拥有这个缓存行的Shared或Exclusive状态,它们会从主内存中重新读取数据,并将缓存行状态设置为Shared。
参考文章:https://cengxuyuan.cn