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 的最新的值。

相关推荐
神明不懂浪漫27 分钟前
【第七章】Java中的常用类
java·开发语言·前端·经验分享·笔记
Jelena1577958579210 小时前
电商运营分析数据比价接口实战:多平台价格监控与智能决策系统
java·大数据·数据库
神明不懂浪漫11 小时前
【第五章】Java中的继承与多态
java·开发语言
AI多Agent协作实战派12 小时前
AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时
java·前端·bug
gaolei_eit13 小时前
Java+Ai+vue
java·spring·maven
qq_25183645713 小时前
基于java Web 动漫视频网站毕业论文
java·开发语言·前端
LayZhangStrive13 小时前
JUC相关的函数、注解、变量杂记
java·面试·多线程·juc
未秃头的程序猿13 小时前
给公司做了个AI客服Agent,用的Spring AI 1.0,3天上线领导拍板了
java·后端·ai编程
曹牧13 小时前
Eclipse 批量文本替换
java·ide·eclipse
程序员清风13 小时前
OpenAI官方发布最新提示词技巧!
java·后端·面试