內(nèi)存屏障由來及實現(xiàn)思路
很多人知道內(nèi)存屏障這個東西應(yīng)該是在學(xué)習(xí)volatile時看到的,但是對內(nèi)存屏障依然存在很多疑惑:為什么要加內(nèi)存屏障?內(nèi)存屏障能解決什么問題?為什么能解決這些問題?
最近在研究volatile的過程中發(fā)現(xiàn)內(nèi)存屏障這東西如果不搞明白,Java中的volatile就別想學(xué)透,所以花了較長時間來研究這塊??戳撕芏噘Y料,寫了很多代碼測試,這篇文章就來總結(jié)下我目前認知中的內(nèi)存屏障。
01 CPU緩存
如果你不了解講內(nèi)存屏障為什么要講CPU緩存,接著往后看。
學(xué)過《計算機組成原理》的同學(xué)應(yīng)該都聽過一個詞:時鐘周期。什么是時鐘周期呢?通俗點來講就是CPU完成一個基本動作需要的時間周期。對硬件有點認識的同學(xué)都知道看CPU好不好一定要看的一個參數(shù):多少多少GHZ。這個GHZ跟時鐘周期之間是存在一定的換算關(guān)系的,感興趣的同學(xué)可以去自行研究。說明一下:不了解這層換算關(guān)系不影響你看后面的內(nèi)容,只要你對時鐘周期有一個基本認知就可以了。
在很早以前,CPU里面是沒有緩存這塊區(qū)域的,就是CPU直接讀寫內(nèi)存。那后面為什么在CPU中增加了緩存呢?因為CPU的運行效率與讀寫內(nèi)存的效率存在著巨大的鴻溝,在讀寫內(nèi)存過程中帶來的等待浪費了很大的CPU算力。現(xiàn)在最新的內(nèi)存是DDR4規(guī)格,但是向內(nèi)存中寫入數(shù)據(jù),據(jù)權(quán)威資料,需要107個CPU時鐘周期,即CPU的運行效率是寫內(nèi)存的107倍。如果CPU只執(zhí)行寫操作需要一個時鐘周期,那CPU等待這個寫完成需要等待106個時鐘周期,是不是很浪費CPU算力?那如何解決呢?就跟我們工作中發(fā)現(xiàn)MySQL出現(xiàn)讀寫瓶頸如何解決是一樣的思維:加緩存。
拿我們今天主流的CPU架構(gòu)來說,現(xiàn)在的CPU主要采用三層緩存:
L1、L2緩存成為本地核心內(nèi)緩存,即一個核一個。如果你的機器是4核,那就是有4個L1+4個L2
L3緩存是所有核共享的。即不管你的CPU是幾核,這個CPU中只有一個L3
L1緩存的大小是64K,即32K指令緩存+32K數(shù)據(jù)緩存。L2是256K,L3是2M。這不是絕對的,目前Intel CPU基本是這樣的設(shè)計
這里還補充一個知識點:緩存行(Cache-line)。緩存行是CPU緩存存儲數(shù)據(jù)的最小單位,大小為64B。這塊如果展開來講要講很久很久,本篇文章就不展開講了,有興趣的同學(xué)可以自行研究。如果你沒有學(xué)習(xí)過計算機硬件相關(guān)知識,可能看不懂。
根據(jù)哲學(xué)的矛盾相對論:任何問題的解決方案都是一個利與弊共存的矛盾體。加緩存的確有效提升了CPU的執(zhí)行效率,但是CPU緩存間的數(shù)據(jù)一致性、CPU緩存與內(nèi)存間的數(shù)據(jù)一致性就是不得不去思考與解決的問題了。而且還得保證解決這兩層數(shù)據(jù)一致性的效率要高于不加緩存前浪費的CPU算力,不然這個方案就是一套偽方案:聽起來高大上,不解決問題。

02 緩存的一致性
MESI協(xié)議就是為了保證CPU各核的緩存、內(nèi)存間的數(shù)據(jù)一致性而生的,沒有了解過的可以百度普及一下,這個比較簡單。這里拓展兩點:
一、CPU運算單元與L1緩存間為什么要增加buffer?CPU實現(xiàn)各個核的緩存與內(nèi)存間的數(shù)據(jù)一致性的思路有點像socket的三次握手:CPU0修改了某個數(shù)據(jù),需要廣播告訴其他CPU,這時候CPU0進入阻塞狀態(tài)等待其他CPU修改其緩存中的狀態(tài),待其他CPU都修改完狀態(tài)返回應(yīng)答消息后才進入運行狀態(tài)。雖然這個阻塞的時間很短,但是在CPU的世界里就很長了,為了保證這部分阻塞時間也能得到充分利用,于是加入了buffer。將預(yù)讀信息存儲進去,這樣CPU解除阻塞后就可以直接從buffer拿出請求處理。
二、MESI協(xié)議的實現(xiàn)思路是:如果CPU0修改了某個數(shù)據(jù),需要廣播給其他CPU,緩存中沒有這個數(shù)據(jù)的CPU丟棄這個廣播消息,緩存中有這個數(shù)據(jù)的CPU監(jiān)聽到這個廣播后會將相應(yīng)的緩存行改為invalid狀態(tài),這樣CPU在下次讀取這個數(shù)據(jù)的時候發(fā)現(xiàn)緩存行失效,就去內(nèi)存中讀取。這里面童鞋們有沒有發(fā)現(xiàn)一個問題:只要存在數(shù)據(jù)修改,CPU就需要去內(nèi)存取數(shù)據(jù),那為什么不實現(xiàn)CPU緩存能共享數(shù)據(jù)呢?這樣CPU在下次讀取的時候去CPU0的緩存行去讀取就可以啦,而且性能更高?,F(xiàn)在的CPU也的確實現(xiàn)了這個思路,對應(yīng)的協(xié)議就是:AMD的MOESI、Intel的MESIF。感興趣的童鞋自己去研究吧。

03 內(nèi)存屏障的由來
對于CPU的寫,目前主流策略有兩種:
1、write back:即CPU向內(nèi)存寫數(shù)據(jù)時,先把真實數(shù)據(jù)放入store buffer中,待到某個合適的時間點,CPU才會將store buffer中的數(shù)據(jù)刷到內(nèi)存中,而且這兩個操作是異步的。這在多線程環(huán)境中,有些情況下是可以接受的,但是有些情況是不可接受的,為了讓程序員有能力根據(jù)業(yè)務(wù)需要達到同步完成,就設(shè)計了內(nèi)存屏障。關(guān)于內(nèi)存屏障,后面會細講。
2、write through:即CPU向內(nèi)存寫數(shù)據(jù)時,同步完成寫store buffer與內(nèi)存。
當前CPU大多數(shù)采用的是write back策略??赡苡型獑柫耍簽槭裁茨兀恳驗榇蠖鄶?shù)情況下,CPU異步完成寫內(nèi)存產(chǎn)生的部分延遲是可以接受的,而且這個延遲極短。只有在多線程環(huán)境下需要嚴格保證內(nèi)存可見等極少數(shù)特殊情況下才需要保證CPU的寫在外界看來是同步完成的,需要借助CPU提供的內(nèi)存屏障實現(xiàn)。如果直接采用策略2:write through,那每次寫內(nèi)存都需要等待數(shù)據(jù)刷入內(nèi)存,極大影響了CPU的執(zhí)行效率。
04 內(nèi)存屏障實現(xiàn)思路
為什么要插入屏障?本質(zhì)是業(yè)務(wù)層面不能接受寫store buffer與刷回內(nèi)存這兩個異步操作產(chǎn)生的哪怕是極少的延遲,即對內(nèi)存可見性的要求極高。
內(nèi)存屏障到底是什么?內(nèi)存屏障什么都不是,它只是一個抽象概念,就像OOP。如果這樣說你不理解,那你把他理解成一堵墻,這堵墻正面與反面的指令無法被CPU亂序執(zhí)行及這堵墻正面與反面的讀寫操作需有序執(zhí)行。
CPU提供了三個匯編指令串行化運行讀寫指令達到實現(xiàn)保證讀寫有序性的目的:
SFENCE:在該指令前的寫操作必須在該指令后的寫操作前完成
LFENCE:在該指令前的讀操作必須在該指令后的讀操作前完成
MFENCE:在該指令前的讀寫操作必須在該指令后的讀寫操作前完成
何謂串行化?你可以理解成CPU把讀、寫、讀寫請求放入了一個隊列,按照先進先出的順序執(zhí)行下去;何謂讀操作完成,即CPU執(zhí)行一次讀操作,把值讀到了寄存器中;何謂寫操作完成,即CPU執(zhí)行一次寫操作,數(shù)據(jù)刷到內(nèi)存中了。
到這里就把內(nèi)存屏障講清楚了,你悟了嗎?
以上就是內(nèi)存屏障由來及實現(xiàn)思路的詳細內(nèi)容,更多關(guān)于內(nèi)存屏障的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中如何將?int[]?數(shù)組轉(zhuǎn)換為?ArrayList(list)
這篇文章主要介紹了Java中將?int[]?數(shù)組?轉(zhuǎn)換為?List(ArrayList),本文通過示例代碼給大家講解的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
Struts2學(xué)習(xí)筆記(4)-通配符的使用
本文主要介紹Struts2中通配符的使用,簡單實用,希望能給大家做一個參考。2016-06-06
Spring Boot 項目啟動自動執(zhí)行方法的兩種實現(xiàn)方式
這篇文章主要介紹了Spring Boot 項目啟動自動執(zhí)行方法的兩種實現(xiàn)方式,幫助大家更好的理解和學(xué)習(xí)使用Spring Boot框架,感興趣的朋友可以了解下2021-05-05
使用IDEA異常斷點來定位java.lang.ArrayStoreException的問題
這篇文章主要介紹了使用IDEA異常斷點來定位java.lang.ArrayStoreException的問題,平常開發(fā)過程中面對這種描述不夠清楚,無法定位具體原因的問題該如何處理,下面我們來一起學(xué)習(xí)一下吧2019-06-06
詳解Spring-boot中讀取config配置文件的兩種方式
這篇文章主要介紹了詳解Spring-boot中讀取config配置文件的兩種方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
JDK1.7 Paths,Files類實現(xiàn)文件夾的復(fù)制與刪除的實例
下面小編就為大家分享一篇JDK1.7 Paths,Files類實現(xiàn)文件夾的復(fù)制與刪除的實例,具有很好的參考價值,希望對大家有所幫助。以前跟隨小編過來看看吧2017-11-11
剖析Java中在Collection集合中使用contains和remove為什么要重寫equals
這篇文章主要介紹了Collection集合的contains和remove方法詳解remove以及相關(guān)的經(jīng)驗技巧,通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-09-09

