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高级特性与最佳实践》