【Java】Volitile的作用、JVM规范如何要求内存屏障、硬件层级内存屏障如何帮助java实现高并发 - 第二天笔记
第二天筆記
Volitile的使用
保持線程可見性
禁止指令重排:單線程中,兩條指令的執行前后順序不會影響執行結果,CPU流水線會優化執行順序
如果存在亂序,則不可能出現x=0,y=0的結果
運行結果:出現了(0,0),說明語句發生了亂序(17、18行之間,或24、25行之間,發生了執行順序交換)
亂序會產生嚴重后果嗎?
DCL單例要不要加Volitile?
對象的創建過程:
1、執行new申請內存空間,m默認值是0
2、invokespecial 調用構造方法,m變為8
3、astore 將棧空間的t和堆空間的內存建立連接
DCL
餓漢式單例模式
懶漢式單例模式:DoubleCheck Lock 雙重檢測鎖
是否可以先在判斷null的時候直接加鎖?從邏輯上沒有問題,但是效率不高。
美團:上述單例的INSTANCE是否需要加Volitile?需要!
惡漢是static的,class load的時候就被new了,是jvm級別的保證(只執行一次),不存在指令重排的問題,在服務起來的時候就初始化了,調用都是初始化之后的事情了
懶漢:加了volitile就能解決這個問題嗎?是的。
JSR內存屏障
JSR規范規定:在任何JVM的實現中,被volitile修飾的變量不能發生重排。這是一個邏輯概念,是JVM層級的要求,不要直接理解為CPU級別的內存屏障。JVM實現為使用lockctl指令。
Load:讀
Store:寫
JVM層級的實現:(這個問題只有阿里問過)
如果要寫,則上面加StoreStore屏障,下面加StoreLoad屏障
如果要讀,則上面加LoadLoad屏障,下面加LoadStore屏障
Happens-Before原則:規定了哪些指令在執行的時候不可以重排序,需要加屏障(以下8種情況)
as if serial:(可能一些書中會提到這個術語)不管如何重排序,單線程執行結果不會改變,不會影響數據最終一致性。
以上都是概念,下面我們來看具體實現
Volatile如何保證一致性?
一般情況下,多核共享一個L3,而每個核心擁有自己的L1,L2
從內存往L3讀數據的時候,按塊讀取,一次讀取一個緩存行cache line,一個緩存行一般為64字節
按塊讀取,是因為根據程序局部性原理,可以提高效率
充分發揮總線 CPU針腳等一次性讀取更多數據的能力
緩存行64字節是怎么確定的?和總線寬度沒有關系,這是一個工業上的折中值。
以緩存行為單位保持一致性:如果一行數據失效了,會通知其他緩存去更新。
這和volitile無關,每個機器都已經實現。見:MESI緩存一致性協議(針對Intel CPU的協議)
其他協議:MSI MESI MOSI Synapse Firefly Dragon
整個程序執行完從3秒優化成1秒多:在變量x前后,添加56字節的冗余變量,這樣保證不再同一個緩存行64字節,不需要來回通知更新。兩個CPU里面訪問的x不需要通知其他CPU
單機最快的消息隊列:鼻祖是RingBuffer。里面進行了很多先顧不到的優化,其中之一就使用了填充緩存行的方式。后來,好多隊列都采用了RingBuffer解決方案。
解決緩存行的偽共享問題,提高效率
拓展
WC - 合并寫技術
WCBuffer只有4個字節,CPU寫的時候要湊滿4字節才寫一次。
下面這個例子可以證明WCBuffer的存在,它的運行結果為,兩次循環比一次循環執行的速度更快,因為湊出了4個字節,寫入效率高。
總結
以上是生活随笔為你收集整理的【Java】Volitile的作用、JVM规范如何要求内存屏障、硬件层级内存屏障如何帮助java实现高并发 - 第二天笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java】多线程Synchronize
- 下一篇: MySQL出现Waiting for t