HTML5技术

就是要你懂Java中volatile关键字实现原理 - 五月的仓颉

字号+ 作者:H5之家 来源:H5之家 2017-06-22 11:00 我要评论( )

原文地址,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用。 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,

原文地址,转载请注明出处,谢谢

 

前言

我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用。

本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好、更正确地地使用volatile关键字。

 

CPU缓存

CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快得多,举个例子:

这种访问速度的显著差异,导致CPU可能会花费很长时间等待数据到来或把数据写入内存。

基于此,现在CPU大多数情况下读写都不会直接访问内存(CPU都没有连接到内存的管脚),取而代之的是CPU缓存,CPU缓存是位于CPU与内存之间的临时存储器,它的容量比内存小得多但是交换速度却比内存快得多。而缓存中的数据是内存中的一小部分数据,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可先从缓存中读取,从而加快读取速度。

按照读取顺序与CPU结合的紧密程度,CPU缓存可分为:

每一级缓存中所存储的数据全部都是下一级缓存中的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也相对递增。

当CPU要读取一个数据时,首先从一级缓存中查找,如果没有再从二级缓存中查找,如果还是没有再从三级缓存中或内存中查找。一般来说每级缓存的命中率大概都有80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取。

 

使用CPU缓存带来的问题

用一张图表示一下CPU-->CPU缓存-->主内存数据读取之间的关系:

当系统运行时,CPU执行计算的过程如下:

如果服务器是单核CPU,那么这些步骤不会有任何的问题,但是如果服务器是多核CPU,那么问题来了,以Intel Core i7处理器的高速缓存概念模型为例(图片摘自《深入理解计算机系统》):

试想下面一种情况:

为了解决这个问题,CPU制造商制定了一个规则:当一个CPU修改缓存中的字节时,服务器中其他CPU会被通知,它们的缓存将视为无效。于是,在上面的情况下,核3发现自己的缓存中数据已无效,核0将立即把自己的数据写回主存,然后核3重新读取该数据。

 

反汇编Java字节码,查看汇编层面对volatile关键字做了什么

有了上面的理论基础,我们可以研究volatile关键字到底是如何实现的。首先写一段简单的代码:

五月的仓颉 LazySingleton { LazySingleton instance = null; LazySingleton getInstance() { 9 if (instance == null) { 10 instance = new LazySingleton(); 11 } instance; 14 } main(String[] args) { 17 LazySingleton.getInstance(); 18 } 19 20 }

首先反编译一下这段代码的.class文件,看一下生成的字节码:

没有任何特别的。要知道,字节码指令,比如上图的getstatic、ifnonnull、new等,最终对应到操作系统的层面,都是转换为一条一条指令去执行,我们使用的PC机、应用服务器的CPU架构通常都是IA-32架构的,这种架构采用的指令集是CISC(复杂指令集),而汇编语言则是这种指令集的助记符。

因此,既然在字节码层面我们看不出什么端倪,那下面就看看将代码转换为汇编指令能看出什么端倪。Windows上要看到以上代码对应的汇编码不难(吐槽一句,说说不难,为了这个问题我找遍了各种资料,差点就准备安装虚拟机,在Linux系统上搞了),访问hsdis工具路径可直接下载hsdis工具,下载完毕之后解压,将hsdis-amd64.dll与hsdis-amd64.lib两个文件放在%JAVA_HOME%\jre\bin\server路径下即可,如下图:

然后跑main函数,跑main函数之前,加入如下虚拟机参数:

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*LazySingleton.getInstance

运行main函数即可,代码生成的汇编指令为:

compileonly *LazySingleton.getInstance 3 Loaded disassembler from D:\JDK\jre\bin\server\hsdis-amd64.dll Argument 0 is unknown.RIP: 0x29312a0 Code size: 0x00000108 ] 8 [Entry Point] 9 [Verified Entry Point] 10 [Constants] # [sp+0x20] (sp of caller) 13 0x00000000029312a0: mov dword ptr [rsp+0ffffffffffffa000h],eax 14 0x00000000029312a7: push rbp 00x00000000029312b6: mov r11d,dword ptr [r10+58h] 0x00000000029312ba: test r11d,r11d 22 0x00000000029312bd: je 29312e0h 0x00000000029312c9: mov r11d,dword ptr [r10+58h] 25 0x00000000029312cd: mov rax,r11 0x00000000029312d4: add rsp,10h 29 0x00000000029312d8: pop rbp 00x00000000029312e0: mov rax,qword ptr [r15+60h] 33 0x00000000029312e4: mov r10,rax 34 0x00000000029312e7: add r10,10h 35 0x00000000029312eb: cmp r10,qword ptr [r15+70h] 36 0x00000000029312ef: jnb 293135bh 37 0x00000000029312f1: mov qword ptr [r15+60h],r10 38 0x00000000029312f5: prefetchnta byte ptr [r10+0c0h] 0x0000000002931303: mov r10,qword ptr [r12+r11*8+0b0h] 41 0x000000000293130b: mov qword ptr [rax],r10 42 0x000000000293130e: mov dword ptr [rax+8h],0e07d00b2h 0x0000000002931315: mov dword ptr [rax+0ch],r12d 0x000000000293131c: mov rdx,rbp 0x0000000002931324: mov r10,rbp 52 0x0000000002931327: shr r10,3h 0x0000000002931335: mov dword ptr [r11+58h],r10d 0x0000000002931343: shr r10,9h 57 0x0000000002931347: mov r11d,20b2000h 58 0x000000000293134d: mov byte ptr [r11+r10],r12l dword ptr [rsp],0h 0x0000000002931356: jmp 29312bfh 000x000000000293136c: jmp 2931319h 68 0x000000000293136e: mov rdx,rax 69 0x0000000002931371: jmp 2931376h 0x0000000002931376: add rsp,10h 72 0x000000000293137a: pop rbp [Stub Code] 0[Exception Handler] [Deopt Handler Code] 80 0x0000000002931394: call 2931399h 81 0x0000000002931399: sub qword ptr [rsp],5h 00000x00000000029313a7: hlt

这么长长的汇编代码,可能大家不知道CPU在哪里做了手脚,没事不难,定位到59、60两行:

0dword ptr [rsp],0h

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • css3+javascript旋转的3d盒子 - 当年华褪去生涩

    css3+javascript旋转的3d盒子 - 当年华褪去生涩

    2017-06-15 14:02

  • 谈一谈Java8的函数式编程 (一) - 祈求者-

    谈一谈Java8的函数式编程 (一) - 祈求者-

    2017-05-25 14:01

  • [JavaScript]自执行函数 - CN_Simo

    [JavaScript]自执行函数 - CN_Simo

    2017-05-25 12:01

  • Docker+SpringBoot+Mybatis+thymeleaf的Java博客系统开源啦 - 13韩

    Docker+SpringBoot+Mybatis+thymeleaf的Java博客系统开源啦 - 13韩

    2017-05-15 16:00

网友点评