什麼樣的資料會存入快取?
編譯器或CPU可以明確知曉的,可能被經常訪問的資料。例如一個在迴圈體中的變數,因為這個變數需要經常訪問,如果每次都從主存中拿,那就太慢了。
快取一致,是跟誰一致?
是跟主存一致,當主存中的對應資料發生變動的時候,CPU中的快取也會隨之變動。例如Cache中快取了變數x的值,當主存中的x的值變動的時候,Cache中的值也會重新整理。“重新整理”可能是將該值標記為“失效”,下次需要時從主存拉取,或是直接用新值覆蓋。
主存的資料變動是如何被監聽到的?
Bus Snooping。匯流排窺探,只要監聽匯流排操作就可知道其他CPU對主存的訪問情況。例如監聽到x變數的寫操作,那就表明x變數的值有變動。
快取記憶體一致這些操作誰來實現?
底層硬體實現,現代計算機都有提供此功能。
那這樣是不是已經有了可見性?
沒有,當然如果可見性指的是“當主存值發生變化的時候,Cache可以看到”,那確實是實現了。問題是可見性不是這麼定義的,它說的是“當一個執行緒對一個變數進行修改時,其他執行緒都能夠看到”。
這兩種表述方式有什麼不同?
其實就是“一個執行緒對變數A的修改” ≠ “主存值發生變化”。也就說,修改變數的值後,修改的只是快取中的值,不會馬上寫入主存。
為什麼不馬上寫入主存?
因為慢。所以這個寫操作會被重排序到後面,這個操作還是會執行的,只是優先順序沒那麼高。
那是不是馬上寫入主存就能實現可見性呢?
是的,只要馬上寫入主存,由於底層提供“快取記憶體一致性”,所以當記憶體中的變數發生變更時,其他CPU的快取也會隨之更新。那這樣可見性就有了。
那麼如何讓它馬上寫入主存呢?
防止重排序就可以了,這樣寫入記憶體的操作就會被立即執行。一般就是加記憶體屏障。synchronized、volatile都依賴記憶體屏障。
什麼時候需要可見性?
正常的程式變數一般都不需要可見性。除非這個變數可能被多個執行緒同時訪問,且你需要用這個變數來協調執行緒操作。那這時候這個變數才需要具有可見性。這時候如果不保證可見性,就很可能出現奇怪的問題,即有時可以正確執行,有時又不行。為什麼呢?因為有時候,這些執行緒剛好在同一CPU上執行,訪問的是同一個Cache,自然就能得到正確的,也就是最新的值。但是,大多數情況不是這樣,多個執行緒會在多個CPU上執行,如果不保證可見性,就可能得到過期的值。然後你就會很奇怪,明明看日誌列印,已經改了啊,為什麼其他執行緒還是沒反應過來呢?
插一句
其實快取一致性和可見性的問題,都是由多CPU的引發的,就是因為每個CPU都有一個Cache,所以才有了這一堆的問題。