volatile 系列之实现原理

我们通过volatile解决了由于编译器的指令重排序导致的可见性问题,这意味着volatile 底层用到了内存屏障,下面我们从它的部分源码中找一下内存屏障相关的痕迹。

通过javap-V VolatileExample.class打印VolatileExample类的字节指令如下。

java 复制代码
public static volatile boolean stop;
    descriptor: z
    flags:ACC_PUBLIC,ACC_STATIC,ACC_VOLATILE

我们可以看到修饰了volatile关键字的属性,多了一个ACC_VOLATILE的flag。这个指令会通过字节码解释器来执行,定位到Hotspot源码的bytecodeInterpreter.cpp文件,找到_putstatic 指令的解析代码。

静态变量的获取和赋值分别通过getstatic和putstatic指令来实现,非静态变量通过getfield 和 putfield 指令来操作stop字段代码如下:

java 复制代码
CASE(_putstatic):
//省略部分代码
int field_offset = cache->f2_as_index(); 
if (cache->is_volatile()) {
    if (tos_type == itos) {
        obj->release_int_field_put(field_offset,STACK_INT(-1));
    } else if (tos_type -= atos){
        VERIFY_OOP(STACK_OBJECT(-1));
        obj->release_obj_field_put(field_offset,STACK_OBJECT(-1));                 
        OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift],0);
    }
    //省略部分代码
    OrderAccess::storeload();
    //省略部分代码
}

上面代码表示,如果当前字段采用volatile 修饰,即 cache->is_volatile(),则根据当前字段类型调用不同的方法进行赋值。

java 复制代码
bool is_volatile    () const    { return (_flags & JVM_ACC_VOLATILE)!=0;}    

在完成stop字段的赋值之后,代码调用了OrderAccess::storeload()内存屏障方法,会基于lock指令来实现内存屏障。

回到某篇文章中演示VolatileExample可见性问题的代码。

java 复制代码
public class VolatileExample {

    public volatile static boolean stop=false;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
        int i=0;
        while(!stop){ //此时t1 线程来加载stop的值,由于当前CPU的缓存行stop已经失效,
所以从main线程的缓存行加载或者直接从内存中加载。
            i++;
        });
        t1.start();
        System.out.println("begin start thread" ); 
        Thread.sleep(1000); stop=true;
        //StoreLoad();//相当于在这里增加了一个内存屏障,该屏障把stop刷新到缓存行
    }
}

上述代码中,对stop增加了volatile关键字之后能够保证可见性的原因是:

  • volatile关键字会在JVM层面声明一个C++的volatile,它能够防止JIT层面的指令重排序。
  • 在对修饰了 volatile关键字的stop字段赋值后,JVM会调用storeload()内存屏障方法,该方法中声明了lock指令,该指令有两个作用。

在CPU层面,给stop赋值的指令会先存储到Store Buffers中,所以lock 指令会使得Store Buffers 中的数据刷新到缓存行。

使得其他CPU 中缓存了stop 的缓存行失效,也就是让存储在Invalidate Queues 中的对 stop 失效的指令立即生效。

当其他线程再去读取stop 的值时,会从内存中或者其他缓存了stop字段的缓存行中重新加载,使得线程能够获得 stop 的最新的值。

相关推荐
Java面试题总结7 小时前
java高频面试题(2026最新)
java·开发语言·jvm·数据库·spring·缓存
苦逼的猿宝7 小时前
学生心理咨询评估系统
java·毕业设计·springboot·计算机毕业设计
隔窗听雨眠7 小时前
doctype、charset、meta如何控制整个渲染流水线
java·服务器·前端
西安邮电大学9 小时前
SpringBean完整生命周期
java·spring
刀法如飞9 小时前
DDD 与 Ontology 对比分析:哪一种更适合AI时代复杂系统构建?
java·架构·领域驱动设计
SunnyDays10119 小时前
Java 读写 Excel 公式:从基础到高级的实战总结
java·开发语言·excel
wb043072019 小时前
Java 26
java·开发语言
白露与泡影9 小时前
JVM GC调优实战:从线上频繁Full GC到RT降低80%的全过程
java·开发语言·jvm
范什么特西9 小时前
Spring 动态代理 静态代理
java·后端·spring
醇氧9 小时前
Spring 动态注册 Bean 深度解析:从源码到实践
java·后端·spring