2010-03-22 57 views
10

我知道,Java中volatile變量的用途是寫入這些變量對其他線程立即可見。我也意識到一個同步塊的效果之一就是將線程本地內存刷新到全局內存。刷新線程局部內存到全局內存是什麼意思?

我從來沒有完全理解在這種情況下對'線程本地'內存的引用。我明白,只存在於堆棧上的數據是線程本地的,但是當談論堆中的對象時,我的理解變得朦朧。

我希望得到以下幾點意見:

  1. 當一臺機器上有多個處理器上執行,並刷新線程本地存儲僅僅指的是CPU緩存到內存的沖洗?

  2. 在單處理器機器上執行時,這是否意味着什麼?

  3. 如果堆有可能在兩個不同的內存位置(每個都由不同的線程訪問)有相同的變量,在什麼情況下會出現這種情況?這對垃圾收集有什麼影響?虛擬機如何積極地做這種事情?

  4. (編輯:添加問題4)退出同步塊時刷新了什麼數據?這是線程在本地的一切嗎?它只是在同步塊內寫入的嗎?

    Object x = goGetXFromHeap(); // x.f is 1 here  
    Object y = goGetYFromHeap(); // y.f is 11 here 
    Object z = goGetZFromHead(); // z.f is 111 here 
    
    y.f = 12; 
    
    synchronized(x) 
    { 
        x.f = 2; 
        z.f = 112; 
    } 
    
    // will only x be flushed on exit of the block? 
    // will the update to y get flushed? 
    // will the update to z get flushed? 
    

總的來說,我認爲想了解是否線程局部手段內存只有一個CPU物理訪問或者如果由VM進行邏輯線程本地堆分配?

任何指向演示或文檔的鏈接都將非常有幫助。我花了很多時間研究這個問題,雖然我發現了很多很好的文獻,但我一直無法滿足我對線程本地存儲器的不同情況定義的好奇心。

非常感謝。

回答

6

你所說的沖水被稱爲「記憶障礙」。這意味着CPU可以確保從RAM看到的內容也可以從其他CPU /內核看到。這意味着兩件事:

  • JIT編譯器刷新CPU寄存器。通常,代碼可以在CPU寄存器中保存一些全局可見數據(例如實例字段內容)的副本。寄存器不能從其他線程看到。因此,​​的一半工作是確保不存在這樣的緩存。

  • ​​實現還執行內存屏障,以確保當前內核對RAM的所有更改都傳播到主RAM(或者至少所有其他內核都知道該內核具有最新值 - 緩存一致性協議可能相當複雜)。

的第二份工作是在單處理器系統微不足道的(我的意思是,與具有單核單CPU系統),但單處理器系統往往成爲時下罕見。

至於線程局部堆,這在理論上可以完成,但它通常不值得付出努力,因爲沒有任何內容可以通過​​來清除內存的哪些部分。這是線程與共享內存模型的侷限性:全部內存應該被共享。在遇到的第一個​​中,JVM應該將其所有「線程本地堆對象」都刷新到主RAM中。

然而,最近來自Sun的JVM可以執行「逃逸分析」,其中JVM成功證明某些實例永遠不會從其他線程可見。這是典型的例如由javac創建的用於處理字符串串聯的StringBuilder實例。如果實例從未作爲參數傳遞給其他方法,則它不會變成「全局可見」。這使得它有資格進行線程本地堆分配,或者甚至在適當的情況下適用於基於堆棧的分配。請注意,在這種情況下,不會有重複;該實例不在「同時在兩個地方」。只有JVM可以將實例保存在不會產生內存屏障成本的私有位置。

+0

感謝您的意見。我其實很熟悉逃生分析,它開始與'線程本地'混淆。我想請問兩個後續問題: 1.如果編譯器已經證明一個對象是線程本地的,並且該對象存在於堆的線程局部區域中,那麼爲什麼要寫入該對象內部一個同步塊需要刷新?從CPU緩存到線程本地堆區域的刷新只能被寫入的線程觀察到?這是線程切換處理器,並開始執行一個不同的CPU緩存? – 2010-03-22 21:32:47

+0

2. JVM有可能在堆上的兩個單獨的內存位置同時存在一個對象嗎?如果是這樣,在什麼情況下會出現這種情況? – 2010-03-22 21:33:22

+0

1.「同步」意味着「全部」的沖洗。 'synchronized'有一個參數,這個參數是獲取鎖的實例,但是Java內存模型要求來自線程的整個內存視圖受到內存障礙的限制。現在,如果JVM可以證明它不需要刷新對象,因爲沒有其他線程可以看到它(並且「未轉義對象」是很好的候選對象),那麼JVM就可以在「as if」規則(JVM只要結果與Java抽象機器無法區分就可以完成它所希望的任何事情)。 – 2010-03-23 12:16:14

1

這實際上是一個實現細節,如果當前未同步的對象的內存內容對另一個線程可見。

當然,有限制,因爲所有的內存不是重複的,並且並不是所有的指令都被重新排序,但重要的是如果底層JVM發現它是一種更優化的方式那。

問題是,堆是真的「正確」存儲在主內存中,但訪問主內存比訪問CPU的緩存或將值保存在CPU內的寄存器中要慢。通過要求將值寫入內存(這是同步的作用,至少在釋放鎖時),它強制寫入主內存。如果JVM可以自由地忽略它,它可以獲得性能。

就一個CPU系統會發生什麼而言,即使在執行另一個線程時,多個線程仍然可以將值保存在緩存或寄存器中。不能保證有任何情況下,其他線程無需同步就可以看到一個值,儘管它顯然更有可能。當然,在移動設備之外,單CPU正在走軟盤之路,所以這不會是一個長期相關的考慮因素。

欲瞭解更多信息,我推薦Java Concurrency in Practice。這真的是一本關於這個主題的實用書。

+0

這是在舊的Java內存模型(JMM)中指定的,但這已經過去了。 – 2010-03-22 20:58:26

+0

感謝您的評論。這是否意味着同步只是將CPU緩存刷新到主內存?或者,有沒有相同的變量可以存在於堆上的兩個不同位置的情況? – 2010-03-22 21:10:25

+0

@Jack,不,它也可以指向指令重新排序(所以事情可以寫入主內存,但以一種看起來錯誤的方式),當然還有鎖定。我無法想象一個JVM實現在非同步代碼中實際上在共享內存中創建一個對象的副本,但我不知道規範中的任何內容都不允許它。 – Yishai 2010-03-22 22:31:37

1

它不像CPU-Cache-RAM那麼簡單。這些都包含在JVM和JIT中,並添加了他們自己的行爲。

看看The "Double-Checked Locking is Broken" Declaration。這是關於爲什麼雙重檢查鎖定不起作用的論述,但它也解釋了Java內存模型的一些細微差別。