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高階特性與最佳實踐》