volatile,還可以有這麼硬的理解

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":" 關鍵字作爲Java虛擬機提供的輕量級同步機制,在Java併發編程中佔據着重要的地位,但是深入理解volatile可不是一件簡單的事,瞭解","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"的同學都知道,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"變量保證了可見性,而可見性又與Java內存模型息息相關,所以本文先簡單介紹內存模型相關概念,然後再從Java虛擬機層面剖析分析volatile變量,接着從硬件層面出發,帶你層層深入瞭解volatile及其背後的故事。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"1、計算機內存模型與Java內存模型的關係","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於現代計算機處理器與存儲設備的運算速度存在幾個數量級的差異,所以現代計算機都會在處理器與主內存之間加上高速緩存作爲緩衝:將處理器計算所需數據複製到高速緩存,處理器直接從高速緩存中獲取數據計算,同時處理器將計算結果放入緩存,再由緩存同步至主內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java虛擬機爲了達到“一次編譯,到處運行”的目的,也有自己的內存模型,即Java內存模型(JMM)。Java內存模型作爲一種規範,屏蔽了各種操作系統和硬件的內存訪問規則,是計算機內存模型的一種邏輯抽象。它規定所有的變量都必須存在主內存中,每個Java線程都有自己的工作內存,工作內存中存放了所需變量的副本,Java線程對變量的操作必須在工作內存中,而不能直接操作主內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/33190143e8908374440e1a5815601353.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image","attrs":{}}]},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖所示,雖然這兩種內存模型都能夠解決運算速度不匹配的問題,但隨之而來就是緩存不一致問題:多個處理器都有自己的高速緩存,但他們又共享同一主內存,從而造成了變量修改不可見問題。爲了解決緩存不一致問題,需要處理器在處理緩存時滿足緩存一致性協議,例如MESI協議。既然有緩存一致性協議的存在,爲什麼還需要volatile關鍵字來保證變量的可見性呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2、volatile變量特徵","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們來說一下volatile變量具備以下特徵:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"可見性","attrs":{}},{"type":"text","text":" ,對於","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"變量的讀,線程總是能讀到當前最新的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"值,也就是任一線程對","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"變量的寫入對其餘線程都是立即可見;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"有序性","attrs":{}},{"type":"text","text":",禁止編譯器和處理器爲了提高性能而進行指令重排序;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"基本不保證原子性","attrs":{}},{"type":"text","text":",由於存在long/double 非原子性協議,long/double在32位x86的hotspot虛擬機下允許沒有被volatile修飾的變量讀寫操作劃分爲兩次進行。但是從JDK9開始,hotspot也明確約束所有數據類型訪問保持原子性,所以volatile變量保證原子性可以基本忽略。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,volatile變量是怎麼保證變量的可見性和有序性的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"3、深入剖析volatile變量","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從Java內存模型層面來說: Java內存模型保證了volatile變量的可見性,也就是說JMM保證新值能馬上同步到主內存,同時把其他線程的工作內存中對應的變量副本置爲無效,以及每次使用前立即從主內存讀取共享變量,那JMM又是如何達到這個目的呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有序性,編譯器和處理器爲了提高運算性能都會對不存在數據依賴的操作進行指令重排優化,在Java內存模型中,通過","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"as-if-serial","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"happens-before(先行先發生)","attrs":{}},{"type":"text","text":" 來保證從重排的正確性,同時對於","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"變量有特殊的規則:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對一個變量的寫操作先行發生於後面對這個變量的讀操作","attrs":{}},{"type":"text","text":",那麼Java內存模型底層是如何實現這一特殊規則的呢?答案就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存屏障(Memory Barrier)","attrs":{}},{"type":"text","text":"。在Java內存模型中,主要有以下4種類型的內存屏障:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LoadLoad屏障:","attrs":{}},{"type":"text","text":" 對於Load1,LoadLoad,Load2這樣的語句,在Load2及後續讀取操作前要保證Load1要讀取的數據讀取完畢;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LoadStore屏障:","attrs":{}},{"type":"text","text":" 對於Load1,LoadStore,Store2這樣的語句,在Store2及後續寫入操作前要保證Load1要讀取的數據讀取完畢;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"StoreStore屏障:","attrs":{}},{"type":"text","text":" 對於Store1,StoreStore,Store2這樣的語句,在Store2及後續寫入操作前要保證Store1的寫入操作對其他處理器可見;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"StoreLoad屏障:","attrs":{}},{"type":"text","text":" 對於Store1,StoreLoad,Load2這樣的語句,在Load2及後續讀取操作前,Store1的寫入對所有處理器可見。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/25/25e68643af118d47b5c8cf40f67d7d10.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210109195819646","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏是不是可以發現:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JMM對於volatile變量的可見性及有序性都是通過內存屏障來實現的","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着,深入分析volatile底層原理,從機器碼的層面看看,對於volatile變量的特性是怎麼實現的,首先我們先看一段代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"public class VolatileTest {\n public static volatile int race = 0;\n public static int value = 0;\n public static void increase() {\n race++;\n value++;\n }\n private static final int THREAD_COUNT = 20;\n public static void main(String[] args) {\n Thread[] threads = new Thread[THREAD_COUNT];\n for (int i = 0; i < THREAD_COUNT; i++) {\n threads[i] = new Thread(() -> {\n for (int j = 0; j < 10000; j++) {\n increase();\n }\n });\n threads[i].start();\n }\n while (Thread.activeCount()> 1) {\n Thread.yield();\n }\n System.out.println(\"race: \" + race + \" value: \" + value);\n }\n}\n複製代碼\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述程序用20個線程對volatile變量race進行累加,每個線程累加10000次,如果能正確的併發執行的話應該是200000纔對,最後多次運行結果都是一個小於200000的數字","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c2/c2e0dcc3ff1e1b8c01d9bece7d78785c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210108152530761","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從這裏也能看出,volatile變量並不能保證原子性,將上面的代碼經過JITWatch工具得到彙編語句如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2b/2bf8121a592589c0523d4e909867d888.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210108194550071","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過彙編指令可以看出,被","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"修飾有一個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"lock","attrs":{}},{"type":"text","text":"指令前綴,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"lock","attrs":{}},{"type":"text","text":"指令的作用是將本地處理器的緩存寫入內存,同時將其他處理器的緩存失效,這樣其他處理需要數據計算時,必須重新讀取主內存的數據,從而達到了變量的可見性的目的;對於禁止指令重排序,同樣也是通過整條lock指令(","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"lock add1$0x0, (%rsp)","attrs":{}},{"type":"text","text":")形成一條內存屏障,來禁止指令重排。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到此,我們已經分析了volatile變量具有的特性,以及JMM是怎麼來實現volatile變量的特性。但是對於文章開頭提出的,既然有緩存一致性協議來保證緩存的一致性,爲什麼還需要由volatile來保證變量的可見性這個問題好像還是沒有答案。接下來將是本文的重點,從硬件層面出發,帶你瞭解高速緩存、MESI協議等原理,層層深入,看完以後一定會對volatile變量有更加深入的理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"4、高速緩存結構與MESI協議分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先高速緩存的內部結構如下所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e5/e5fa8733c49142545cdf28de58f021e5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-cache-struct","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"高速緩存內部是一個拉鍊散列表,是不是很眼熟,是的,和HashMap的內部結構十分相似,高速緩存中分爲很多桶,每個桶裏用鏈表的結構連接了很多cache entry,在每一個cache entry內部主要由三部分內容組成:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"tag:","attrs":{}},{"type":"text","text":" 指向了這個緩存數據在主內存中的數據的地址","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"cache line:","attrs":{}},{"type":"text","text":" 存放多個變量數據","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"flag:","attrs":{}},{"type":"text","text":" 緩存行狀態","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由此引出了MESI緩存一致性協議,MESI協議對所有處理器有如下約定:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"各個處理器在操作內存數據時,都會往總線發送消息,各個處理器還會不停的從總線嗅探消息,通過這個消息來保證各個處理器的協作","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時MESI中有以下兩個操作:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"flush操作:","attrs":{}},{"type":"text","text":" 強制處理器在更新完數據後,將更新的數據(可能寫緩衝器、寄存器中)刷到高速緩存或者主內存(不同的硬件實現MESI協議的方式不一樣),同時向總線發出信息說明自己修改了某一數據","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"refresh操作:","attrs":{}},{"type":"text","text":" 從總線嗅探到某一數據失效後,將該數據在自己的緩存中失效,然後從更新後的處理器高速緩存或主內存中加載數據到自己的高速緩存中","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們來說明在兩個處理器情況下,其中一個處理器(處理器0)要修改數據的整個過程。假定數據所在cache line在兩個高速緩存中都處於S(Shared)狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1d/1d202862725b739c29b033d12244b1d0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"cpu_process","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、處理器0發送invalidate消息到總線;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、處理器1在總線上進行嗅探,嗅探到invalidate消息後,通過地址解析定位到對應的cache line,發現此時cache line的狀態爲S,則將cache line的狀態改爲I,同時返回invalidate ack消息到總線;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、處理器0在總線在嗅探到所有(例子中只有處理器1)的invalidate ack後,將要修改的cache line狀態置爲E(Exclusive),表示要進行獨佔修改,修改完以後將cache line狀態置爲M(Modified),同時可能將數據刷回主內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個過程中,如有其他處理器要修改處理器0中的cache line狀態將會被阻塞。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時,假如此時處理器1要讀取相應的cache line數據,則會發現狀態爲I(Invalid)。於是處理器1向總線中發出read消息,處理器0嗅探到read消息後,將會從自己的高速緩存或者主內存中將數據發送到總線,並將自身對應的cache line狀態置爲S(Shared),處理器1從總線中接收到read消息後,將最新的數據寫入到對應的cache line,並將狀態置爲S(Shared)。由此處理0與處理器1中對應的cache line狀態又都變成了S(Shared)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更新和讀取數據的過程如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/50/5046598176e0d99e10aa812ab4291c10.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210109211606795","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0b432e53031c0beaa9698b2d46f80991.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210109211645122","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MESI協議能保證各個處理器間的高速緩存數據一致性,但是同樣帶來兩個嚴重的效率問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當處理器0向總線發送invalidate消息後,要等到所有其他擁有相同緩存的處理器返回invalidate ack消息才能將對應的cache line狀態置爲E並進行修改,但是在這過程中它一直是處於阻塞狀態,這將嚴重影響處理器的性能","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當處理1嗅探到invalidate消息後,會先去將對應的cache line狀態置爲I,然後纔會返回invalidate ack消息到總線,這個過程也是影響性能的。 基於以上兩個問題,設計者又引入了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫緩衝器","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"無效隊列","attrs":{}},{"type":"text","text":"。 在上面的場景中,處理器0,先將要修改的數據放入","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫緩衝器","attrs":{}},{"type":"text","text":",再向總線發出invalidate消息來通知其他有相同緩存的處理器緩存失效,處理器0就可以繼續執行其他指令,當接收到其他所有處理器的invalidate ack後,再將處理器0中的cache line置爲E,並將","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫緩衝器","attrs":{}},{"type":"text","text":"中的數據寫入高速緩存。處理器1從總線嗅探到invalidate消息後,先將消息放入到無效隊列,接着立刻返回invalidate ack消息。這樣來提高處理的速度,達到提高性能的目的。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77028eb6f2f8e0af66346d8a3acff955.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210110143559471","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫緩衝器","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"無效隊列","attrs":{}},{"type":"text","text":"帶來的問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫緩衝器","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"無效隊列","attrs":{}},{"type":"text","text":"提高MESI協議下處理器性能,但同時也帶來了新的可見性與有序性問題如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/35fbd879a944542f2de22f674139f9ef.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image-20210110150401017","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖所示:假設最初共享變量x=0同時存在於處理0和處理1的高速緩存中,且對應狀態爲S(Shared),此時處理0要將x的值改變成1,先將值寫到寫緩衝器裏,然後向總線發送invalidate消息,同時處理器1希望將x的值加1賦給y,此時處理器1發現自身緩存中x=0狀態爲S,則直接用x=0進行參與計算,從而發生了錯誤,顯然這個錯誤由寫緩衝器和無效隊列導致的,因爲x的新值還在寫緩衝器中,無效消息在處理1的無效隊列中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決這個問題出現了寫屏障(Store Barrier)和讀屏障(Load Barrier)兩種內存屏障。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫屏障","attrs":{}},{"type":"text","text":":強制將寫緩衝器中的內容寫入到高速緩存中,或者將屏障之後的指令全部寫到寫緩衝器直到之前寫緩衝器中的內容全部被刷回緩存中,也就是處理0必須等到所有的invalidate ack消息後,才能執行後續的操作,相當於flush操作;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"讀屏障","attrs":{}},{"type":"text","text":":處理器在讀取數據前,必須強制檢查無效隊列中是否有invalidate消息,如果有必須先處理完無效隊列彙總的無效消息,再進行數據讀取,相當於refresh操作。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過加入讀寫屏障保證了可見性與有序性。之所以說保證了有序性,是因爲指令亂序現象就是寫緩衝器異步接收到其他處理器中的invalidate ack消息後,再執行寫緩衝器中的內容,導致本應該執行的指令順序發生錯亂。通過加入寫屏障後保證了異步操作之後才能執行後續的指令,保證了原來的指令順序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在分析JMM保證volatile變量的有序性和可見性問題時,同樣我們也說到是通過四種內存屏障的來實現的,那麼上面的讀/寫屏障和JMM中四種內存屏障有什麼關聯呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫屏障與(StoreStore、StoreLoad)屏障的關係:在volatile變量寫之前加入StoreSore屏障保證了volatile寫之前,寫緩衝器中的內容已全部刷回告訴緩存,防止前面的寫操作和volatile寫操作之間發生指令重排,在volatile寫之後加入StoreLoad屏障,保證了後面的讀/寫操作與volatile寫操作發生指令重排,所以寫屏障同時具有StoreStore與StoreLoad的功能","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讀屏障與(LoadLoad、LoadStore)屏障的關係:在volatile變量讀之後加入LoadLoad屏障保證了後面其他讀操作的無效隊列中無效消息已經被刷回到了高速緩存,在volatile變量讀操作後加入LoadStore屏障,保證了後面其他寫操作的無效隊列中無效消息已經被刷回高速緩存。讀屏障同時具有了LoadLoad,LoadStore的功能。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏,對於文章開頭提出:既然存在MESI緩存一致性協議爲什麼還要","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"volatile","attrs":{}},{"type":"text","text":"關鍵字來保證可見性和有序性的問題是不是就很清楚了呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:肖說一下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鏈接:","attrs":{}},{"type":"link","attrs":{"href":"https://links.jianshu.com/go?to=https%3A%2F%2Fjuejin.cn%2Fpost%2F6919432286232379400","title":null},"content":[{"type":"text","text":"https://juejin.cn/post/6919432286232379400","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章