volidate关键字解析

1.CPU中的一级缓存,二级缓存,三级缓存

缓存又叫高速缓冲存储器,其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。 缓存的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当CPU下次再用这些信息时,它就不必访问慢速的主存,而直接从快速的CACHE中得到,从而提高了得到这些信息的速度,使CPU有更高的运行效率。 缓存的工作原理:是当CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在缓存中,只有大约10%需要从内存读取。这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存。

缓存的大小:一般说来,更大一点的cache容量,对提高命中率是有好处的,由于cache是用价格很高的静态存储器SRAM器件实现的,而cache容量达到一定大小后,再增加其容量,对命中率的提高并不明显,从合理的性能/价格比考虑,cache的容量设置应在一个合理的容量范围之内。

缓存要分一级二级 三级,是为了建立一个层次存储结构,以达到最高性价比。而且多级组织还可以提高cache的命中率,提高执行效能。

一般来说,一级缓存可以分为一级数据缓存(Data Cache,D-Cache)和一级指令缓存(InstructionCache,I-Cache)。二者分别用来存放数据以及对执行这些数据的指令进行即时解码,而且两者可以同时被CPU访问,减少了争用Cache所造成的冲突,提高了处理器效能。 目前大多数CPU的一级数据缓存和一级指令缓存具有相同的容量,例如AMD的Athlon。XP就具有64KB的一级数据缓存和64KB的一级指令缓存,其一级缓存就以64KB+64KB来表示,其余的CPU的一级缓存表示方法以此类推。并不是缓存越大越好,譬如AMD和INTER就有不同的理论,AMD认为一级缓存越大越好,所以一级比较大,而INTER认为过大会有更长的指令执行时间,所以一级很小,二级缓存那两个公司的理论又反过来了,AMD的小,INTER的大,一般主流的INTERCPU的2级缓存都在2M左右,我们通常用(L1,L2)来称呼。

CPU缓存(CacheMemory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多,但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。由此可见,在CPU中加入缓存是一种高效的解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速度,又有内存的大容量的存储系统了。缓存对CPU的性能影响很大,主要是因为CPU的数据交换顺序和CPU与缓存间的带宽引起的。 根据数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。 一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。

目前缓存基本上都是采用SRAM存储器,SRAM是英文StaticRAM的缩写,它是一种具有静志存取功能的存储器,不需要刷新电路即能保存它内部存储的数据。不像DRAM内存那样需要刷新电路,每隔一段时间,固定要对DRAM刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积,这也是目前不能将缓存容量做得太大的重要原因。 它的特点归纳如下:优点是节能、速度快、不必配合内存刷新电路、可提高整体的工作效率,缺点是集成度低、相同的容量体积较大、而且价格较高,只能少量用于关键性系统以提高效率。

2.并发CPU缓存问题,伪共享和内存行

伪共享指的是在多个线程同时读写同一个缓存行的不同变量的时候,尽管这些变量之间没有任何关系,但是在多个线程之间仍然需要同步,从而导致性能下降的情况。在对称多处理器结构的系统中,伪共享是影响性能的主要因素之一,由于很难通过走查代码的方式定位伪共享的问题,因此,大家把伪共享称为“性能杀手”。

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

程序以及数据被加载到主内存 指令和数据被加载到CPU缓存 CPU执行指令,把结果写到高速缓存 高速缓存中的数据写回主内存

从上图看到,线程1在CPU核心1上读写变量X,同时线程2在CPU核心2上读写变量Y,不幸的是变量X和变量Y在同一个缓存行上,每一个线程为了对缓存行进行读写,都要竞争并获得缓存行的读写权限,如果线程2在CPU核心2上获得了对缓存行进行读写的权限,那么线程1必须刷新它的缓存后才能在核心1上获得读写权限,这导致这个缓存行在不同的线程间多次通过L3缓存来交换最新的拷贝数据,这极大的影响了多核心CPU的性能。如果这些CPU核心在不同的插槽上,性能会变得更糟。

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


public class LazySingleton {

    private static volatile LazySingleton instance = null;
    
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        
        return instance;
    }
    
    public static void main(String[] args) {
        LazySingleton.getInstance();
    }
    
}
复制代码
编译后使用javap -c 查看该段代码的字节码表示如下:
Compiled from "LazySingleton.java"
public class com.example.validate.LazySingleton {
  public com.example.validate.LazySingleton();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static com.example.validate.LazySingleton getInstance();
    Code:
       0: getstatic     #2                  // Field instance:Lcom/example/validate/LazySingleton;
       3: ifnonnull     16
       6: new           #3                  // class com/example/validate/LazySingleton
       9: dup
      10: invokespecial #4                  // Method "<init>":()V
      13: putstatic     #2                  // Field instance:Lcom/example/validate/LazySingleton;
      16: getstatic     #2                  // Field instance:Lcom/example/validate/LazySingleton;
      19: areturn

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #5                  // Method getInstance:()Lcom/example/validate/LazySingleton;
       3: pop
       4: return

  static {};
    Code:
       0: aconst_null
       1: putstatic     #2                  // Field instance:Lcom/example/validate/LazySingleton;
       4: return
}

复制代码

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

下面就看看将代码转换为汇编指令能看出什么端倪。Windows上要看到以上代码对应的汇编码不难,可下载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

CompilerOracle: compileonly *LazySingleton.getInstance
Loaded disassembler from D:\Java\jdk1.8.0_172\jre\bin\server\hsdis-amd64.dll
Decoding compiled method 0x0000000003485210:
Code:
Argument 0 is unknown.RIP: 0x3485380 Code size: 0x00000248
[Disassembling for mach='amd64']
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x0000000017892a88} 'getInstance' '()Lcom/example/validate/LazySingleton;' in 'com/example/validate/LazySingleton'
  #           [sp+0x40]  (sp of caller)
  0x0000000003485380: mov     dword ptr [rsp+0ffffffffffffa000h],eax
  0x0000000003485387: push    rbp
  0x0000000003485388: sub     rsp,30h
  0x000000000348538c: mov     rdx,17892d18h     ;   {metadata(method data for {method} {0x0000000017892a88} 'getInstance' '()Lcom/example/validate/LazySingleton;' in 'com/example/validate/LazySingleton')}
  0x0000000003485396: mov     esi,dword ptr [rdx+0dch]
  0x000000000348539c: add     esi,8h
  0x000000000348539f: mov     dword ptr [rdx+0dch],esi
  0x00000000034853a5: mov     rdx,17892a80h     ;   {metadata({method} {0x0000000017892a88} 'getInstance' '()Lcom/example/validate/LazySingleton;' in 'com/example/validate/LazySingleton')}
  0x00000000034853af: and     esi,0h
  0x00000000034853b2: cmp     esi,0h
  0x00000000034853b5: je      34854aah
  0x00000000034853bb: mov     rdx,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x00000000034853c5: mov     edx,dword ptr [rdx+68h]  ;*getstatic instance
                                                ; - com.example.validate.LazySingleton::getInstance@0 (line 14)

  0x00000000034853c8: cmp     rdx,0h
  0x00000000034853cc: mov     rdx,17892d18h     ;   {metadata(method data for {method} {0x0000000017892a88} 'getInstance' '()Lcom/example/validate/LazySingleton;' in 'com/example/validate/LazySingleton')}
  0x00000000034853d6: mov     rsi,108h
  0x00000000034853e0: jne     34853f0h
  0x00000000034853e6: mov     rsi,118h
  0x00000000034853f0: mov     rdi,qword ptr [rdx+rsi]
  0x00000000034853f4: lea     rdi,[rdi+1h]
  0x00000000034853f8: mov     qword ptr [rdx+rsi],rdi
  0x00000000034853fc: jne     3485491h          ;*ifnonnull
                                                ; - com.example.validate.LazySingleton::getInstance@3 (line 14)

  0x0000000003485402: mov     rdx,100060828h    ;   {metadata('com/example/validate/LazySingleton')}
  0x000000000348540c: mov     rax,qword ptr [r15+60h]
  0x0000000003485410: lea     rdi,[rax+10h]
  0x0000000003485414: cmp     rdi,qword ptr [r15+70h]
  0x0000000003485418: jnbe    34854c1h
  0x000000000348541e: mov     qword ptr [r15+60h],rdi
  0x0000000003485422: mov     rcx,qword ptr [rdx+0a8h]
  0x0000000003485429: mov     qword ptr [rax],rcx
  0x000000000348542c: mov     rcx,rdx
  0x000000000348542f: shr     rcx,3h
  0x0000000003485433: mov     dword ptr [rax+8h],ecx
  0x0000000003485436: xor     rcx,rcx
  0x0000000003485439: mov     dword ptr [rax+0ch],ecx
  0x000000000348543c: xor     rcx,rcx           ;*new  ; - com.example.validate.LazySingleton::getInstance@6 (line 15)

  0x000000000348543f: mov     rdx,rax
  0x0000000003485442: mov     rsi,17892d18h     ;   {metadata(method data for {method} {0x0000000017892a88} 'getInstance' '()Lcom/example/validate/LazySingleton;' in 'com/example/validate/LazySingleton')}
  0x000000000348544c: add     qword ptr [rsi+128h],1h
  0x0000000003485454: mov     rdx,rax           ;*invokespecial <init>
                                                ; - com.example.validate.LazySingleton::getInstance@10 (line 15)

  0x0000000003485457: mov     qword ptr [rsp+20h],rax
  0x000000000348545c: nop
  0x000000000348545d: nop
  0x000000000348545e: nop
  0x000000000348545f: call    33c61a0h          ; OopMap{[32]=Oop off=228}
                                                ;*invokespecial <init>
                                                ; - com.example.validate.LazySingleton::getInstance@10 (line 15)
                                                ;   {optimized virtual_call}
  0x0000000003485464: mov     rax,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x000000000348546e: mov     rsi,qword ptr [rsp+20h]
  0x0000000003485473: mov     r10,rsi
  0x0000000003485476: mov     dword ptr [rax+68h],r10d
  0x000000000348547a: shr     rax,9h
  0x000000000348547e: mov     rsi,1232d000h
  0x0000000003485488: mov     byte ptr [rax+rsi],0h
  0x000000000348548c: lock add dword ptr [rsp],0h  ;*putstatic instance
                                                ; - com.example.validate.LazySingleton::getInstance@13 (line 15)

  0x0000000003485491: mov     rax,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x000000000348549b: mov     eax,dword ptr [rax+68h]  ;*getstatic instance
                                                ; - com.example.validate.LazySingleton::getInstance@16 (line 18)

  0x000000000348549e: add     rsp,30h
  0x00000000034854a2: pop     rbp
  0x00000000034854a3: test    dword ptr [1470100h],eax
                                                ;   {poll_return}
  0x00000000034854a9: ret
  0x00000000034854aa: mov     qword ptr [rsp+8h],rdx
  0x00000000034854af: mov     qword ptr [rsp],0ffffffffffffffffh
  0x00000000034854b7: call    3483e20h          ; OopMap{off=316}
                                                ;*synchronization entry
                                                ; - com.example.validate.LazySingleton::getInstance@-1 (line 14)
                                                ;   {runtime_call}
  0x00000000034854bc: jmp     34853bbh
  0x00000000034854c1: mov     rdx,rdx
  0x00000000034854c4: call    347ca40h          ; OopMap{off=329}
                                                ;*new  ; - com.example.validate.LazySingleton::getInstance@6 (line 15)
                                                ;   {runtime_call}
  0x00000000034854c9: jmp     348543fh
  0x00000000034854ce: nop
  0x00000000034854cf: nop
  0x00000000034854d0: mov     rax,qword ptr [r15+2a8h]
  0x00000000034854d7: mov     r10,0h
  0x00000000034854e1: mov     qword ptr [r15+2a8h],r10
  0x00000000034854e8: mov     r10,0h
  0x00000000034854f2: mov     qword ptr [r15+2b0h],r10
  0x00000000034854f9: add     rsp,30h
  0x00000000034854fd: pop     rbp
  0x00000000034854fe: jmp     33ef1a0h          ;   {runtime_call}
  0x0000000003485503: hlt
  0x0000000003485504: hlt
  0x0000000003485505: hlt
  0x0000000003485506: hlt
  0x0000000003485507: hlt
  0x0000000003485508: hlt
  0x0000000003485509: hlt
  0x000000000348550a: hlt
  0x000000000348550b: hlt
  0x000000000348550c: hlt
  0x000000000348550d: hlt
  0x000000000348550e: hlt
  0x000000000348550f: hlt
  0x0000000003485510: hlt
  0x0000000003485511: hlt
  0x0000000003485512: hlt
  0x0000000003485513: hlt
  0x0000000003485514: hlt
  0x0000000003485515: hlt
  0x0000000003485516: hlt
  0x0000000003485517: hlt
  0x0000000003485518: hlt
  0x0000000003485519: hlt
  0x000000000348551a: hlt
  0x000000000348551b: hlt
  0x000000000348551c: hlt
  0x000000000348551d: hlt
  0x000000000348551e: hlt
  0x000000000348551f: hlt
[Stub Code]
  0x0000000003485520: nop                       ;   {no_reloc}
  0x0000000003485521: nop
  0x0000000003485522: nop
  0x0000000003485523: nop
  0x0000000003485524: nop
  0x0000000003485525: mov     rbx,0h            ;   {static_stub}
  0x000000000348552f: jmp     348552fh          ;   {runtime_call}
[Exception Handler]
  0x0000000003485534: call    33eeb20h          ;   {runtime_call}
  0x0000000003485539: mov     qword ptr [rsp+0ffffffffffffffd8h],rsp
  0x000000000348553e: sub     rsp,80h
  0x0000000003485545: mov     qword ptr [rsp+78h],rax
  0x000000000348554a: mov     qword ptr [rsp+70h],rcx
  0x000000000348554f: mov     qword ptr [rsp+68h],rdx
  0x0000000003485554: mov     qword ptr [rsp+60h],rbx
  0x0000000003485559: mov     qword ptr [rsp+50h],rbp
  0x000000000348555e: mov     qword ptr [rsp+48h],rsi
  0x0000000003485563: mov     qword ptr [rsp+40h],rdi
  0x0000000003485568: mov     qword ptr [rsp+38h],r8
  0x000000000348556d: mov     qword ptr [rsp+30h],r9
  0x0000000003485572: mov     qword ptr [rsp+28h],r10
  0x0000000003485577: mov     qword ptr [rsp+20h],r11
  0x000000000348557c: mov     qword ptr [rsp+18h],r12
  0x0000000003485581: mov     qword ptr [rsp+10h],r13
  0x0000000003485586: mov     qword ptr [rsp+8h],r14
  0x000000000348558b: mov     qword ptr [rsp],r15
  0x000000000348558f: mov     rcx,62a2ed00h     ;   {external_word}
  0x0000000003485599: mov     rdx,3485539h      ;   {internal_word}
  0x00000000034855a3: mov     r8,rsp
  0x00000000034855a6: and     rsp,0fffffffffffffff0h
  0x00000000034855aa: call    626e60c0h         ;   {runtime_call}
  0x00000000034855af: hlt
[Deopt Handler Code]
  0x00000000034855b0: mov     r10,34855b0h      ;   {section_word}
  0x00000000034855ba: push    r10
  0x00000000034855bc: jmp     33c7600h          ;   {runtime_call}
  0x00000000034855c1: hlt
  0x00000000034855c2: hlt
  0x00000000034855c3: hlt
  0x00000000034855c4: hlt
  0x00000000034855c5: hlt
  0x00000000034855c6: hlt
  0x00000000034855c7: hlt
Decoding compiled method 0x0000000003490bd0:
Code:
Argument 0 is unknown.RIP: 0x3490d20 Code size: 0x00000108
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x0000000017892a88} 'getInstance' '()Lcom/example/validate/LazySingleton;' in 'com/example/validate/LazySingleton'
  #           [sp+0x20]  (sp of caller)
  0x0000000003490d20: mov     dword ptr [rsp+0ffffffffffffa000h],eax
  0x0000000003490d27: push    rbp
  0x0000000003490d28: sub     rsp,10h           ;*synchronization entry
                                                ; - com.example.validate.LazySingleton::getInstance@-1 (line 14)

  0x0000000003490d2c: mov     r10,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x0000000003490d36: mov     r10d,dword ptr [r10+68h]
                                                ;*getstatic instance
                                                ; - com.example.validate.LazySingleton::getInstance@0 (line 14)

  0x0000000003490d3a: test    r10d,r10d
  0x0000000003490d3d: je      3490d5ch
  0x0000000003490d3f: mov     r10,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x0000000003490d49: mov     r11d,dword ptr [r10+68h]
  0x0000000003490d4d: mov     rax,r11           ;*getstatic instance
                                                ; - com.example.validate.LazySingleton::getInstance@16 (line 18)

  0x0000000003490d50: add     rsp,10h
  0x0000000003490d54: pop     rbp
  0x0000000003490d55: test    dword ptr [1470000h],eax
                                                ;   {poll_return}
  0x0000000003490d5b: ret
  0x0000000003490d5c: mov     rax,qword ptr [r15+60h]
  0x0000000003490d60: mov     r10,rax
  0x0000000003490d63: add     r10,10h
  0x0000000003490d67: cmp     r10,qword ptr [r15+70h]
  0x0000000003490d6b: jnb     3490dd7h
  0x0000000003490d6d: mov     qword ptr [r15+60h],r10
  0x0000000003490d71: prefetchw byte ptr [r10+0c0h]
  0x0000000003490d79: mov     r10d,2000c105h    ;   {metadata('com/example/validate/LazySingleton')}
  0x0000000003490d7f: shl     r10,3h
  0x0000000003490d83: mov     r10,qword ptr [r10+0a8h]
  0x0000000003490d8a: mov     qword ptr [rax],r10
  0x0000000003490d8d: mov     dword ptr [rax+8h],2000c105h
                                                ;   {metadata('com/example/validate/LazySingleton')}
  0x0000000003490d94: mov     dword ptr [rax+0ch],r12d
  0x0000000003490d98: mov     rbp,rax           ;*new  ; - com.example.validate.LazySingleton::getInstance@6 (line 15)

  0x0000000003490d9b: mov     rdx,rbp
  0x0000000003490d9e: nop
  0x0000000003490d9f: call    33c61a0h          ; OopMap{rbp=Oop off=132}
                                                ;*invokespecial <init>
                                                ; - com.example.validate.LazySingleton::getInstance@10 (line 15)
                                                ;   {optimized virtual_call}
  0x0000000003490da4: mov     r10,rbp
  0x0000000003490da7: mov     r11,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x0000000003490db1: mov     dword ptr [r11+68h],r10d
  0x0000000003490db5: mov     r10,0d66f87d0h    ;   {oop(a 'java/lang/Class' = 'com/example/validate/LazySingleton')}
  0x0000000003490dbf: shr     r10,9h
  0x0000000003490dc3: mov     r11d,1232d000h
  0x0000000003490dc9: mov     byte ptr [r11+r10],r12l
  0x0000000003490dcd: lock add dword ptr [rsp],0h  ;*putstatic instance
                                                ; - com.example.validate.LazySingleton::getInstance@13 (line 15)

  0x0000000003490dd2: jmp     3490d3fh
  0x0000000003490dd7: mov     rdx,100060828h    ;   {metadata('com/example/validate/LazySingleton')}
  0x0000000003490de1: nop
  0x0000000003490de3: call    33eec20h          ; OopMap{off=200}
                                                ;*new  ; - com.example.validate.LazySingleton::getInstance@6 (line 15)
                                                ;   {runtime_call}
  0x0000000003490de8: jmp     3490d98h          ;*new  ; - com.example.validate.LazySingleton::getInstance@6 (line 15)

  0x0000000003490dea: mov     rdx,rax
  0x0000000003490ded: jmp     3490df2h
  0x0000000003490def: mov     rdx,rax           ;*invokespecial <init>
                                                ; - com.example.validate.LazySingleton::getInstance@10 (line 15)

  0x0000000003490df2: add     rsp,10h
  0x0000000003490df6: pop     rbp
  0x0000000003490df7: jmp     348dc20h          ;   {runtime_call}
  0x0000000003490dfc: hlt
  0x0000000003490dfd: hlt
  0x0000000003490dfe: hlt
  0x0000000003490dff: hlt
[Stub Code]
  0x0000000003490e00: mov     rbx,0h            ;   {no_reloc}
  0x0000000003490e0a: jmp     3490e0ah          ;   {runtime_call}
[Exception Handler]
  0x0000000003490e0f: jmp     33ef4a0h          ;   {runtime_call}
[Deopt Handler Code]
  0x0000000003490e14: call    3490e19h
  0x0000000003490e19: sub     qword ptr [rsp],5h
  0x0000000003490e1e: jmp     33c7600h          ;   {runtime_call}
  0x0000000003490e23: hlt
  0x0000000003490e24: hlt
  0x0000000003490e25: hlt
  0x0000000003490e26: hlt
  0x0000000003490e27: hlt


复制代码

这么长长的汇编代码,可能大家不知道CPU在哪里做了手脚 我截取下关键部分

  0x0000000003490dcd: lock add dword ptr [rsp],0h  ;*putstatic instance
    ; - com.example.validate.LazySingleton::getInstance@13 (line 15)
复制代码

这里的关键就是add前面的lock指令,后面详细分析一下lock指令的作用和为什么加上lock指令后就能保证volatile关键字的内存可见性。

之前有说过IA-32架构,关于CPU架构的问题大家有兴趣的可以自己查询一下,这里查询一下IA-32手册关于lock指令的描述,

lock指令的几个作用:

1.锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放,不过实际后来的处理器都采用锁缓存替代锁总线,因为锁总线的开销比较大,锁总线期间其他CPU没法访问内存

2.lock后的写操作会回写已修改的数据,同时让其它CPU相关缓存行失效,从而重新从主存中加载最新的数据

3.不是内存屏障却能完成类似内存屏障的功能,阻止屏障两遍的指令重排序

(1)中写了由于效率问题,实际后来的处理器都采用锁缓存来替代锁总线,这种场景下多缓存的数据一致是通过缓存一致性协议来保证的,我们来看一下什么是缓存一致性协议。

3.缓存一致性协议

讲缓存一致性之前,先说一下缓存行的概念:

缓存是分段(line)的,一个段对应一块存储空间,我们称之为缓存行,它是CPU缓存中可分配的最小存储单元,大小32字节、64字节、128字节不等,这与CPU架构有关,通常来说是64字节。当CPU看到一条读取内存的指令时,它会把内存地址传递给一级数据缓存,一级数据缓存会检查它是否有这个内存地址对应的缓存段,如果没有就把整个缓存段从内存(或更高一级的缓存)中加载进来。注意,这里说的是一次加载整个缓存段,这就是上面提过的局部性原理 上面说了,LOCK#会锁总线,实际上这不现实,因为锁总线效率太低了。因此最好能做到:使用多组缓存,但是它们的行为看起来只有一组缓存那样。缓存一致性协议就是为了做到这一点而设计的,就像名称所暗示的那样,这类协议就是要使多组缓存的内容保持一致。

缓存一致性协议有多种,但是日常处理的大多数计算机设备都属于"嗅探(snooping)"协议,它的基本思想是:

所有内存的传输都发生在一条共享的总线上,而所有的处理器都能看到这条总线:缓存本身是独立的,但是内存是共享资源,所有的内存访问都要经过仲裁(同一个指令周期中,只有一个CPU缓存可以读写内存)。

CPU缓存不仅仅在做内存传输的时候才与总线打交道,而是不停在嗅探总线上发生的数据交换,跟踪其他缓存在做什么。所以当一个缓存代表它所属的处理器去读写内存时,其它处理器都会得到通知,它们以此来使自己的缓存保持同步。只要某个处理器一写内存,其它处理器马上知道这块内存在它们的缓存段中已失效。

这里的I、S和M状态已经有了对应的概念:失效/未载入、干净以及脏的缓存段。所以这里新的知识点只有E状态,代表独占式访问,这个状态解决了"在我们开始修改某块内存之前,我们需要告诉其它处理器"这一问题:只有当缓存行处于E或者M状态时,处理器才能去写它,也就是说只有在这两种状态下,处理器是独占这个缓存行的。当处理器想写某个缓存行时,如果它没有独占权,它必须先发送一条"我要独占权"的请求给总线,这会通知其它处理器把它们拥有的同一缓存段的拷贝失效(如果有)。只有在获得独占权后,处理器才能开始修改数据----并且此时这个处理器知道,这个缓存行只有一份拷贝,在我自己的缓存里,所以不会有任何冲突。

反之,如果有其它处理器想读取这个缓存行(马上能知道,因为一直在嗅探总线),独占或已修改的缓存行必须先回到"共享"状态。如果是已修改的缓存行,那么还要先把内容回写到内存中。

4.由lock指令回看volatile变量读写

工作内存Work Memory其实就是对CPU寄存器和高速缓存的抽象,或者说每个线程的工作内存也可以简单理解为CPU寄存器和高速缓存。

那么当写两条线程Thread-A与Threab-B同时操作主存中的一个volatile变量i时,Thread-A写了变量i,那么:

Thread-A发出LOCK#指令 发出的LOCK#指令锁总线(或锁缓存行),同时让Thread-B高速缓存中的缓存行内容失效 Thread-A向主存回写最新修改的i Thread-B读取变量i,那么:

Thread-B发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值 由此可以看出,volatile关键字的读和普通变量的读取相比基本没差别,差别主要还是在变量的写操作上。

参考文章:

1.PrintAssembly查看volatile汇编代码小记

2.《Java并发编程的艺术》

3.《深入理解Java虚拟机:JVM高级特性与最佳实践》

4.聊聊高并发(三十四)Java内存模型那些事(二)理解CPU高速缓存的工作原理

5.就是要你懂Java中volatile关键字实现原理

6.Java并发:volatile内存可见性和指令重排

7.面试必问的volatile,你了解多少?

转载于:https://juejin.im/post/5c35cd9de51d45522a41f3a0

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值