面时莫慌 | 你好,请谈谈volatile关键字?(四)

1,469 阅读4分钟

这是我参与更文挑战的第28天,活动详情查看: 更文挑战
紧接着上一篇你好,请谈谈volatile关键字?(三)

4.5 内存屏障(memory barrier)

上文讲到指令重排序会带来可见性的问题,解决的方式是使用内存屏障。什么是内存屏障?

内存屏障是一组处理器指令(硬件相关),用于对内存操作顺序做一些限制。可以用来实现多线程访问内存上同一资源的可见性。

4.5.1 借助Hsdis工具分析内存屏障

Hsdis是一个反汇编的库,用在Java运行时分析JIT编译器生成的代码。它是一个dll(动态链接库)文件,放在${JAVA_HOME}/jre/bin/server目录下使用。我们借助这个工具对比变量前加volatile关键字和不加关键字的汇编指令的差异。

首先在Java安装目录配置Hsdis环境,其实就是把两个文件放到指定目录。

hsdis.png

在配置好Hsdis环境后,在运行程序的VM Options加入以下参数。

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*VolatileExample.*
  • -XX:+UnlockDiagnosticVMOptions 参数代表开启诊断模式,以方便打印PrintAssembly一类的信息。
  • -XX:+PrintAssembly 参数代表打印即时编译的二进制信息
  • XX:CompileCommand=compileonly,*VolatileExample.* 这个表示过滤仅显示满足*VolatileExample.*正则表达式的信息。

然后我们运行第二章节的例子VolatileExample

发现加volatile修饰的变量run汇编指令为

0x0000000003565073: lock add dword ptr [rsp],0h  ;*putstatic run

未加volatile修饰的变量run汇编指令为

  0x00000000030e5ceb: push    0ffffffffc4834801h  ;*putstatic run

可以很清晰的看到差异,有volatile修饰的变量,多了一个lock指令。lock是一个汇编控制指令,这个指令相当于实现了一种内存屏障。

4.5.2 详解内存屏障

对于Java语言,内存屏障不是直接由JVM暴露出来提供使用的,而是由JVM根据代码语义,插入到底层运行指令中的。比如上文实践分析出,加入volatile关键字修饰变量,汇编指令上就会多出lock指令。

不同硬件暴露出来供外界使用内存屏障指令是不一样的。比如我们熟知的X86计算机设备,它提供了Ifence(读屏障)Sfence(写屏障)mfence(全屏障)这几种执行内存屏障的指令。

  • 读屏障(Load Memory Barrier)就是在读屏障之后的读操作,都在读屏障之后执行。这个指令要配合写屏障一起完成,达到的效果是,写屏障之前的内存更新在读屏障之后的读操作是可见的。其实质是,在读屏障之前,先应用所有已经在失效队列中的失效操作的指令。
  • 写屏障(Store Memory Barrier)就是在写屏障之前的写操作,都必须要由Store Buffer同步刷新到主存中。达到的效果是,写屏障之后的读写操作都能看到写屏障之前的内存更新。
  • 全屏障(Full Memory Barrier)就是在全屏障之前的读写操作,其产生的内存的更新,对该屏障之后的读写操作都可见。

所以对于4.4.3章节的例子,我们简单修改一下,加入内存屏障,实现可见性。

int value =1;
bool finish = false;

void runOnCPU1(){
  value = 2;
  storeMemoryBarrier();//伪代码,写屏障,强制变量刷新到主存
  finish = true;
}

void runOnCPU2(){
  if(finish){
    loadMemoryBarrier();//伪代码,读屏障,获取多线程环境下最新的变量值
	assert value == 2;
  }
}

所以总结下来,Java 编译器在 volatile 字段的读写操作前后各插入一些内存屏障,解决了指令重排序的问题。这里要特别提到的是,对于Java语言,解决指令重排序的真正的功臣是JMM(Java Memory Model)
JMM作为沟通复杂硬件底层实现的桥梁,通过提供了一些合理的禁用缓存以及禁止重排序的方法,然后在编译时转换成具体的CPU指令,解决了可见性有序性问题,具体的方法包括synchronizedvolatilefinal。这一章节,不做深入,后续进行详细分析。
篇幅较长,继续阅读请点击【面时莫慌】你好,请谈谈volatile关键字?(五)


哥佬倌,莫慌到走!觉好留个赞,探讨上评论。欢迎关注面试专栏面时莫慌 | Java并发编程,面试加薪不用愁。也欢迎关注我,一定做一个长更的好男人。