R大有時會顛覆你對Java的認知

  • 2019 年 10 月 6 日
  • 筆記

關於volatile變數的記憶體可見性,我在JVM群中拋出了一個問題,然後我的一些認知就被顛覆了。

問題

請教一個問題,a,b,c三個變數,其中c是volatile的,a,b是普通變數, a = 1, b = 2, c = 3, c寫入之後,a,b的值也會被刷入快取嗎,還是c寫入之前所有在cpu快取的數據都會被刷入記憶體,還是只刷入和c在同一個快取行的數據?

一開始,我的認知是後者,只刷入同一個快取行的數據。

R大問答

你們要按happens before來考慮這種問題,不要整天無謂想著擦車(cache),Java的修正過的記憶體模型其實基本點很簡單,同一執行緒內的副作用按程式順序發生,所以a、b、c的賦值如果是在同一執行緒內按這個順序寫的,實際執行就要按照這個順序發生(至少表象上要按照這個順序;在程式無法感知順序差異時可以作弊)

這樣就是a賦值happens before b賦值,b賦值happens before c賦值,而不同執行緒之間的操作則是沒有happens before關係的,除非有volatile或者synchronized等帶有跨執行緒happen before關係的操作。不同執行緒之間的非volatile、非synchronized操作直接要想有傳遞的happens before關係的話,中間就肯定得有能產生happens before關係的volatile或者synchronized操作。

什麼暫存器啊、快取啊啥的不必扯進來。

我的反應

哦~~~問號臉。。。

R大繼續解釋

JVM實現的時候是要把這些概念映射下去的(這裡的概念應該就是happens before),但是當你在思考高層程式語義的時候卻拿不合適的低層語義去解釋就很彆扭,映射下去的辦法就是先有高層語義,然後看具體硬體上提供了哪些原語,然後再去實現。例如說在SPARC上它默認是TSO(total store order)的,在上面需要手工做的同步操作就很簡單。

曉銘大大的總結

JIT的時候會去查操作數的屬性,如果是volatile會在讀寫操作附近生成barrier的中間表示,最終barrier中間表示會變成什麼指令,那要根據具體的機器,Memory consistency是一個spec,各種硬體系統包括cache都是實現的細節。

總結

總結起來,我這個問題問的不好,沒水準,問題直接從jmm跳到了硬體具體實現,具體實現都不一樣,中間還隔著一層硬體的memory model。

所以快取到底怎麼刷,不同的CPU、不同廠商、不同型號實現可能都不一樣,一個人想了解所有似乎是個不可能的任務,也不實用,只需要搞清楚JMM層面的東西就OK,管它到底怎麼刷。