原文來自個人博客月泉的博客html
食用該篇文章,做者建議你最好已經提早了解過:JMM以及CPU緩存一致性協議還有相關的內存屏障的知識而且可以理解CPU的亂序執行,若是做者理解不當,歡迎指出。 若是本文對你有所幫助不妨給 博客的Github點個小星星,提問和文章有什麼地方理解錯了也能夠直接在Github上提交issue
該篇文章討論的議題:java
咱們從一個很常見的案例開始出發linux
public class Test { public static void main(String[] args) throws InterruptedException { Demo demo = new Demo(); demo.setName("demo-thread"); demo.start(); Thread.sleep(1000); demo.flag = false; demo.join(); System.out.println(demo.getName() + "線程執行完畢:" + demo.count); } static class Demo extends Thread{ boolean flag = true; int count = 0; @Override public void run(){ while (flag){ count ++; } } } }
這段代碼在執行時,會發生沒法中止下來的現象,顯而易見,demo-thread
從主內存中讀取出flag
的值爲true
放在了工做內存中,而後while
去判斷該值是否爲true
若是爲true
就會不斷的循環,直至flag
的值爲false
爲止,而後在main
方法的線程中修改了demo
實例中的flag的值,但demo-thread
線程並無感知到flag
的值已經被main
線程修改成true
了,從而發生了沒法中止的現象,簡單點來講這就是線程之間的可見性問題,簡單的畫個圖加深下理解。c++
那麼how to 解決呢?就是咱們這篇的主角volatile
(實際上解決的方式有多種,我這裏是爲了寫這篇文章因此這樣解決)git
public class Test { public static void main(String[] args) throws InterruptedException { ................... } static class Demo extends Thread{ volatile boolean flag = true; ........... } }
經過volatile
關鍵字來修飾該變量,使得該變量的修改是對其它線程可見的,同時volatile
還會禁止指令優化的重排序在修飾完volatile
後會在對該變量執行操做時插入內存屏障,在進入下一個議題以前先說說何爲內存屏障。github
先說說處理器的內存屏障,再來討論下JVM定義的內存屏障,首先理解內存屏障本質是什麼,在本質上的內存屏障實際上就是一類同步屏障指令,加了屏障的地方,假如屏障前有讀寫操做以及屏障後也有讀寫操做,那麼屏障前的讀寫操做必然必須先於屏障後的讀寫操做,屏障後的讀寫操做也必然必須後於屏障前的讀寫操做(這裏若是理解了亂序執行應該很好理解)windows
處理器的內存屏障緩存
保障早於屏障以前的讀操做以後再執行晚於屏障的讀操做sass
保障早於屏障以前的寫操做以後再執行晚於屏障的寫操做多線程
保障早於屏障以前的讀寫操做以後再執行晚於屏障以後的讀寫操做
先來認識幾個語義的指令,由於待會在JVM的實現中也會發現它的身影,這裏簡單闡述一下
acquire
: 屏障前的指令不會被排到屏障後去
release
: 屏障後的指令不會被排到屏障前去
fence
:屏障前的指令不會被排到屏障後去,屏障後的指令也不會排到屏障前去
JVM的內存屏障
LoadLoad
讀屏障:例若有指令Load1
和Load2
那麼假如插入屏障指令爲Load1;LoadLoad;Load2
,在中間插入LoadLoad
屏障能夠保障讀操做不會進行亂序優化,即Load2
在執行時,Load1
的讀操做應是執行完了的。
StoreStore
寫屏障:例若有指令Store1
和Store2
那麼假如插入屏障指令爲Store1;StoreSotre;Store2
,在中間插入StoreStore
屏障能夠保障寫操做不會進行亂序優化,即Store2
在執行時,Store1
的寫操做應是執行完了的,而且Sotre1
的寫操做是對Store2
可見的,至於爲何可見,我會在說完這四種屏障時給出答案。
LoadStore
讀寫屏障:例若有指令Load1
和Store2
那麼假如插入屏障指令爲Load1;LoadStore;Store2
,在中間插入LoadStore
屏障能夠保障前面的讀操做和後面的寫操做不會被亂序優化,即Store2
執行時,Load1
應是執行完了的
StoreLoad
寫讀屏障:例若有指令Store1
和Load2
那麼假如插入屏障的指令爲Store1;StoreLoad;Load2
,在中間插入StoreLoad
屏障能夠保障前面的寫操做對後面的讀操做不會被亂序優化,而且是可見的,即Load2
執行時,Store1
應是執行完了的,並其寫操做對屏障後的讀操做可見
若是不管是加了何種屏障,例如LoadStore
屏障即屏障前的讀操做都會從主存中讀取值,屏障後的指令會往主存寫值,再例如StoreLoad
屏障即屏障前的寫操做會往主存寫值,從而對屏障後的讀操做可見,固然僅僅是往主存寫值也不能保障就是可見的,因此後面的讀操做也是從主存中讀值
So,如今是否是對volatile
很清晰了,也知道上面爲何加了volatile
關鍵字後,變量是對其它線程可見的了吧?,從而使得咱們的flag
修改後可以在多線程環境下可以有效,以一個很是簡單的例子,深刻的探討下去。
public class Demo{ static volatile int i; public static void main(String[] args){ i = 1; } }
查看生成的字節碼(部分片斷)
static volatile int i; descriptor: I flags: ACC_STATIC, ACC_VOLATILE
能夠看到在字節碼文件上有一個ACC_VOLATILE
的標識符,接着打開JVM(我用的Hotspot)的代碼來看看吧~
能夠在JVM源碼中看到有一個is_volatile
判斷是不是volatile
訪問限定符修飾的,而後再看字節碼解釋器的部分源碼
注意有三個細節,首先會判斷是否標識了volatile
,而後再判斷類型,咱們這個是int
類型因此會調用release_int_field_put
,最後插入一道屏障storeload
,首先先看itos
的定義
顧名思義:表示棧頂緩存的int類型數據
接着看release_int_field_put
會發現它調用了OrderAccess::release_store
那麼這個方法到底是幹什麼的呢?首先注意方法參數添加了volatile
關鍵字,這是c++的volatile
關鍵字和Java(java語法也有這個同名的關鍵字不是嗎?)被該關鍵字所修飾的變量意味着易變的,再c++中修飾了這個關鍵字的變量每次使用時都會從變量對應的內存地址且編譯器也不會對它進行優化。
那麼os::atomic_copy64
又是什麼呢?這裏會針對不一樣的系統,我這裏只看Linux的
粗暴點,就是生成彙編代碼去拷貝值吧?
接着咱們看
接着看OrderAccess::storeload
請告訴我這4個東西眼熟不眼熟?!?,這固然只是定義不一樣系統下實現不同,咱們這裏仍是看linux
的
看這個方法的實現,還有其它三種的實現是什麼?本篇文章上面也對該語義進行了闡述吧?繼續看linux
下的實現
FULL_MEM_BARRIER
是什麼本文上面也說了吧
針對的環境不通實現也不一樣,這裏具體就再也不闡述
= = 寫到這個小節的時候筆者是用的windows了,因此再補一下windows對fence實現的源碼
看下在我機器上生成的彙編代碼
[Disassembling for mach='amd64'] [Entry Point] [Verified Entry Point] [Constants] # {method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo' # parm0: rdx:rdx = '[Ljava/lang/String;' # [sp+0x40] (sp of caller) 0x00000000037e5320: mov dword ptr [rsp+0ffffffffffffa000h],eax 0x00000000037e5327: push rbp 0x00000000037e5328: sub rsp,30h 0x00000000037e532c: mov rsi,17cf2af8h ; {metadata(method data for {method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo')} 0x00000000037e5336: mov edi,dword ptr [rsi+0dch] 0x00000000037e533c: add edi,8h 0x00000000037e533f: mov dword ptr [rsi+0dch],edi 0x00000000037e5345: mov rsi,17cf2a30h ; {metadata({method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo')} 0x00000000037e534f: and edi,0h 0x00000000037e5352: cmp edi,0h 0x00000000037e5355: je 37e537eh ;*iconst_1 ; - org.yuequan.thread.test.Demo::main@0 (line 6) 0x00000000037e535b: mov rsi,0d5b0dad0h ; {oop(a 'java/lang/Class' = 'org/yuequan/thread/test/Demo')} 0x00000000037e5365: mov edi,1h 0x00000000037e536a: mov dword ptr [rsi+68h],edi 0x00000000037e536d: lock add dword ptr [rsp],0h ;*putstatic i ; - org.yuequan.thread.test.Demo::main@1 (line 6) 0x00000000037e5372: add rsp,30h 0x00000000037e5376: pop rbp 0x00000000037e5377: test dword ptr [2f20100h],eax ; {poll_return} 0x00000000037e537d: ret 0x00000000037e537e: mov qword ptr [rsp+8h],rsi 0x00000000037e5383: mov qword ptr [rsp],0ffffffffffffffffh 0x00000000037e538b: call 37e20a0h ; OopMap{rdx=Oop off=112} ;*synchronization entry ; - org.yuequan.thread.test.Demo::main@-1 (line 6) ; {runtime_call} 0x00000000037e5390: jmp 37e535bh 0x00000000037e5392: nop 0x00000000037e5393: nop 0x00000000037e5394: mov rax,qword ptr [r15+2a8h] 0x00000000037e539b: mov r10,0h 0x00000000037e53a5: mov qword ptr [r15+2a8h],r10 0x00000000037e53ac: mov r10,0h 0x00000000037e53b6: mov qword ptr [r15+2b0h],r10 0x00000000037e53bd: add rsp,30h 0x00000000037e53c1: pop rbp 0x00000000037e53c2: jmp 374ece0h ; {runtime_call} 0x00000000037e53c7: hlt 0x00000000037e53c8: hlt 0x00000000037e53c9: hlt 0x00000000037e53ca: hlt 0x00000000037e53cb: hlt 0x00000000037e53cc: hlt 0x00000000037e53cd: hlt 0x00000000037e53ce: hlt 0x00000000037e53cf: hlt 0x00000000037e53d0: hlt 0x00000000037e53d1: hlt 0x00000000037e53d2: hlt 0x00000000037e53d3: hlt 0x00000000037e53d4: hlt 0x00000000037e53d5: hlt 0x00000000037e53d6: hlt 0x00000000037e53d7: hlt 0x00000000037e53d8: hlt 0x00000000037e53d9: hlt 0x00000000037e53da: hlt 0x00000000037e53db: hlt 0x00000000037e53dc: hlt 0x00000000037e53dd: hlt 0x00000000037e53de: hlt 0x00000000037e53df: hlt [Exception Handler] [Stub Code] 0x00000000037e53e0: call 3750aa0h ; {no_reloc} 0x00000000037e53e5: mov qword ptr [rsp+0ffffffffffffffd8h],rsp 0x00000000037e53ea: sub rsp,80h 0x00000000037e53f1: mov qword ptr [rsp+78h],rax 0x00000000037e53f6: mov qword ptr [rsp+70h],rcx 0x00000000037e53fb: mov qword ptr [rsp+68h],rdx 0x00000000037e5400: mov qword ptr [rsp+60h],rbx 0x00000000037e5405: mov qword ptr [rsp+50h],rbp 0x00000000037e540a: mov qword ptr [rsp+48h],rsi 0x00000000037e540f: mov qword ptr [rsp+40h],rdi 0x00000000037e5414: mov qword ptr [rsp+38h],r8 0x00000000037e5419: mov qword ptr [rsp+30h],r9 0x00000000037e541e: mov qword ptr [rsp+28h],r10 0x00000000037e5423: mov qword ptr [rsp+20h],r11 0x00000000037e5428: mov qword ptr [rsp+18h],r12 0x00000000037e542d: mov qword ptr [rsp+10h],r13 0x00000000037e5432: mov qword ptr [rsp+8h],r14 0x00000000037e5437: mov qword ptr [rsp],r15 0x00000000037e543b: mov rcx,6601c4e0h ; {external_word} 0x00000000037e5445: mov rdx,37e53e5h ; {internal_word} 0x00000000037e544f: mov r8,rsp 0x00000000037e5452: and rsp,0fffffffffffffff0h 0x00000000037e5456: call 65cd4510h ; {runtime_call} 0x00000000037e545b: hlt [Deopt Handler Code] 0x00000000037e545c: mov r10,37e545ch ; {section_word} 0x00000000037e5466: push r10 0x00000000037e5468: jmp 3727600h ; {runtime_call} 0x00000000037e546d: hlt 0x00000000037e546e: hlt 0x00000000037e546f: hlt Decoding compiled method 0x00000000037e4ed0: Code: Argument 0 is unknown.RIP: 0x37e5020 Code size: 0x00000110 [Entry Point] [Verified Entry Point] [Constants] # {method} {0x0000000017cf2a38} 'main' '([Ljava/lang/String;)V' in 'org/yuequan/thread/test/Demo' # parm0: rdx:rdx = '[Ljava/lang/String;' # [sp+0x40] (sp of caller) 0x00000000037e5020: mov dword ptr [rsp+0ffffffffffffa000h],eax 0x00000000037e5027: push rbp 0x00000000037e5028: sub rsp,30h ;*iconst_1 ; - org.yuequan.thread.test.Demo::main@0 (line 6) 0x00000000037e502c: mov rsi,0d5b0dad0h ; {oop(a 'java/lang/Class' = 'org/yuequan/thread/test/Demo')} 0x00000000037e5036: mov edi,1h 0x00000000037e503b: mov dword ptr [rsi+68h],edi 0x00000000037e503e: lock add dword ptr [rsp],0h ;*putstatic i ; - org.yuequan.thread.test.Demo::main@1 (line 6) 0x00000000037e5043: add rsp,30h 0x00000000037e5047: pop rbp 0x00000000037e5048: test dword ptr [2f20100h],eax ; {poll_return} 0x00000000037e504e: ret 0x00000000037e504f: nop 0x00000000037e5050: nop 0x00000000037e5051: mov rax,qword ptr [r15+2a8h] 0x00000000037e5058: mov r10,0h 0x00000000037e5062: mov qword ptr [r15+2a8h],r10 0x00000000037e5069: mov r10,0h 0x00000000037e5073: mov qword ptr [r15+2b0h],r10 0x00000000037e507a: add rsp,30h 0x00000000037e507e: pop rbp 0x00000000037e507f: jmp 374ece0h ; {runtime_call} 0x00000000037e5084: hlt 0x00000000037e5085: hlt 0x00000000037e5086: hlt 0x00000000037e5087: hlt 0x00000000037e5088: hlt 0x00000000037e5089: hlt 0x00000000037e508a: hlt 0x00000000037e508b: hlt 0x00000000037e508c: hlt 0x00000000037e508d: hlt 0x00000000037e508e: hlt 0x00000000037e508f: hlt 0x00000000037e5090: hlt 0x00000000037e5091: hlt 0x00000000037e5092: hlt 0x00000000037e5093: hlt 0x00000000037e5094: hlt 0x00000000037e5095: hlt 0x00000000037e5096: hlt 0x00000000037e5097: hlt 0x00000000037e5098: hlt 0x00000000037e5099: hlt 0x00000000037e509a: hlt 0x00000000037e509b: hlt 0x00000000037e509c: hlt 0x00000000037e509d: hlt 0x00000000037e509e: hlt 0x00000000037e509f: hlt [Exception Handler] [Stub Code] 0x00000000037e50a0: call 3750aa0h ; {no_reloc} 0x00000000037e50a5: mov qword ptr [rsp+0ffffffffffffffd8h],rsp 0x00000000037e50aa: sub rsp,80h 0x00000000037e50b1: mov qword ptr [rsp+78h],rax 0x00000000037e50b6: mov qword ptr [rsp+70h],rcx 0x00000000037e50bb: mov qword ptr [rsp+68h],rdx 0x00000000037e50c0: mov qword ptr [rsp+60h],rbx 0x00000000037e50c5: mov qword ptr [rsp+50h],rbp 0x00000000037e50ca: mov qword ptr [rsp+48h],rsi 0x00000000037e50cf: mov qword ptr [rsp+40h],rdi 0x00000000037e50d4: mov qword ptr [rsp+38h],r8 0x00000000037e50d9: mov qword ptr [rsp+30h],r9 0x00000000037e50de: mov qword ptr [rsp+28h],r10 0x00000000037e50e3: mov qword ptr [rsp+20h],r11 0x00000000037e50e8: mov qword ptr [rsp+18h],r12 0x00000000037e50ed: mov qword ptr [rsp+10h],r13 0x00000000037e50f2: mov qword ptr [rsp+8h],r14 0x00000000037e50f7: mov qword ptr [rsp],r15 0x00000000037e50fb: mov rcx,6601c4e0h ; {external_word} 0x00000000037e5105: mov rdx,37e50a5h ; {internal_word} 0x00000000037e510f: mov r8,rsp 0x00000000037e5112: and rsp,0fffffffffffffff0h 0x00000000037e5116: call 65cd4510h ; {runtime_call} 0x00000000037e511b: hlt [Deopt Handler Code] 0x00000000037e511c: mov r10,37e511ch ; {section_word} 0x00000000037e5126: push r10 0x00000000037e5128: jmp 3727600h ; {runtime_call} 0x00000000037e512d: hlt 0x00000000037e512e: hlt 0x00000000037e512f: hlt
這麼長腫麼看?看關鍵部位就好拉
看到是使用的lock
指令吧?那麼問題來了lock
指令是什麼,我在這裏膚淺的解釋一下:CPU提供了在執行指令期間提供了總線加鎖的手段,那麼加了lock
的彙編生成機器碼就使CPU在執行這條指令的時候會把#HLOCK pin的電位拉低,持續到這條指令結束時放開,從而把總線鎖住,從而保證這條指令執行的原子性
經過內存屏障來提醒編譯器和CPU不對指令進行優化防止其優化亂序執行從而達到有序性,在屏障的先後都是經過主存讀寫達到線程之間的可見性。(O(∩_∩)O 我想不用再多解釋了)
就好比在多線程中,多個線程對於一個實例變量的變量i
進行自增操做,例如i++
,這時候就產生了競態條件致使,例如給i
變量加5000次,獲得的結果多是5000獲得的結果也多是少於5000,儘管你加了volatile
,這是爲何呢?要知道volatile
僅是經過內存屏障的機制來保障
例如
load1;load2;store1;store2;StoreLoad;load3;store3.....
儘管你保證了可見性,但你不能保證該操做的原子性,所謂的原子性本質就是指令在執行期間不被打斷,要麼就是不執行,仔細想一想i++
是否是一個三步的複合操做:取值、相加、賦值,就好比:你在未賦值時別的線程執行時,別的線程也正在處以賦值狀態,語言解釋好麻煩看下列示例
i= 0 線程A 線程B 取值 0 取值 0 相加 1 相加 1 賦值 1 賦值 1
儘管你保障了可見性,但你並不能保證你拿到手上的值永遠是最新值。