Java被設計爲跨平臺的語言,在內存管理上,顯然也要有一個統一的模型。而且Java語言最大的特點就是廢除了指針,把程序員從痛苦中解脫出來,不用再考慮內存使用和管理方面的問題。
可惜世事總不盡如人意,雖然JMM設計上方便了程序員,但是它增加了虛擬機的複雜程度,而且還導致某些編程技巧在Java語言中失效。
JMM主要是爲了規定了線程和內存之間的一些關係。對Java程序員來說只需負責用synchronized同步關鍵字,其它諸如與線程/內存之間進行 數據交換/同步等繁瑣工作均由虛擬機負責完成。如圖1所示:根據JMM的設計,系統存在一個主內存(Main Memory),Java中所有變量都儲存在主存中,對於所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存中保存的是主存中某些變量的拷貝,線程對所有變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主 存完成。
圖1 Java內存模型示例圖
線程若要對某變量進行操作,必須經過一系列步驟:首先從主存複製/刷新數據到工作內存,然後執行代碼,進行引用/賦值操作,最後把變量內容寫回Main Memory。Java語言規範(JLS)中對線程和主存互操作定義了6個行爲,分別爲load,save,read,write,assign和 use,這些操作行爲具有原子性,且相互依賴,有明確的調用先後順序。具體的描述請參見JLS第17章。
我們在前面的章節介紹了synchronized的作用,現在,從JMM的角度來重新審視synchronized關鍵字。
假設某條線程執行一個synchronized代碼段,其間對某變量進行操作,JVM會依次執行如下動作:
(1) 獲取同步對象monitor (lock)
(2) 從主存複製變量到當前工作內存 (read and load)
(3) 執行代碼,改變共享變量值 (use and assign)
(4) 用工作內存數據刷新主存相關內容 (store and write)
(5) 釋放同步對象鎖 (unlock)
可見,synchronized的另外一個作用是保證主存內容和線程的工作內存中的數據的一致性。如果沒有使用synchronized關鍵字,JVM 不保證第2步和第4步會嚴格按照上述次序立即執行。因爲根據JLS中的規定,線程的工作內存和主存之間的數據交換是鬆耦合的,什麼時候需要刷新工作內存或 者更新主內存內容,可以由具體的虛擬機實現自行決定。如果多個線程同時執行一段未經synchronized保護的代碼段,很有可能某條線程已經改動了變 量的值,但是其他線程卻無法看到這個改動,依然在舊的變量值上進行運算,最終導致不可預料的運算結果。