## 13 問題的根源—Java內存模型簡介
> 世界上最寬闊的是海洋,比海洋更寬闊的是天空,比天空更寬闊的是人的胸懷。
> ——雨果
JAVA內存模型即JMM(Java Memory Model),有些人會和Java內存結構混淆。雖然兩者名字很接近,但描述的為不同內容。Java內存結構描述的是JVM對內存的邏輯劃分,我們在學習垃圾回收和JVM優化的時候會關心JVM內存結構。而本節所講的JMM,實際上是一種規范。它描述了Java程序的運行行為,包括多線程操作對共享內存讀取時,所能讀取到的值應該遵守的規則。
## 1\. JMM的必要性
隨著CPU的不斷發展,CPU的性能越來越強大,但受迫于頻率提升的困難,現代CPU架構開始向多核發展。而作為軟件開發人員為了充分使用CPU的性能,越來越多的開發者會選擇多線程程序開發。CPU在計算時會做一些優化,這些優化對于單線程程序來說是沒有問題的,但對多線程程序則不是那么的友好。
計算機在運行時,絕大多數時間都會把對象信息保存在內存中。但是在此期間,編譯器、處理器或者緩存都可能會把變量從分配的內存中取出處理再放回。比如我們在while循環中判斷flag是否為true來執行一段特定的邏輯,那么編譯器為了優化可能會選擇把flag值取到緩存中。此時主存中的flag值可能會被其它線程所改變,但是此線程是無法感知的。直到某個特定的時機觸發此線程從主存中刷新flag值。所有這些優化都是為了程序有更好的性能。在單線程的程序中,這種優化對于用戶來講是毫無感知的,不過多線程的程序中,這種優化有些時候會造成難以預料的結果。
JMM允許編譯器和緩存保持對數據操作順序優化的自由度。除非程序使用Synchronized或者volatile顯式的告訴處理器需要確保可見性。這意味著如果你沒有進行同步,那么多線程程序對于數據的操作,將會呈現不同的順序。也就是前面一節講的有序性,我們基于代碼順序對數據賦值順序的推論,在多線程程序中可能會不成立。
在JMM之前,C和C++并沒有顯式的內存模型。C語言的內存模型繼承自執行程序的處理器。這意味著并發的C語言程序,在不同的處理器機構上可能會呈現不一樣的結果。但JMM使得Java程序能夠在任何JVM上表現出一樣的行為。當然,現在C和C++也有了內存模型。

## 2\. JMM簡介
JMM為程序中的所有操作定義了一定的規則,叫做Happens-Before。無論兩個操作是否在同一個線程,如果要想保證操作A能看到操作B的結果,那么A、B之間一定要滿足Happens-Before關系。如果兩者間不滿足Hapen-Before關系,JVM可以對其任意重排序。
當多個線程同時讀寫同一個變量,但這些操作間又沒有滿足Happens-Before關系,那么這些線程對此變量存在數據競爭,整個程序將會陷入混亂之中。假如我們在操作共享變量時采用了同步,那么無論有多少線程,對此變量的操作都會呈現出串行一致性。從而使得多線程的操作順序遵守JMM約定。
## 3\. Happens-Before規則
Happens-Before在多線程領域具有重大意義,它可以指導你如何開發多線程的程序,而不至于陷入混亂之中。你所開發的多線程程序,如果想對共享變量的操作符合你設想的順序,那么需要依照Happens-Before原則來開發。happens-before并不是指操作A先于操作B發生,而是指操作A的結果在什么情況下可以被后面操作B所獲取。下面我們就來看一下Happens-before原則。
1. 程序順序規則。如果程序中A操作在B操作之前,那么線程中A操作將在B操作前執行。
2. 上鎖原則。不同線程對同一個鎖的lock操作一定在unclock前。
3. volatile變量原則。對于volatile變量的寫操作會早于對其的讀操作。
4. 線程啟動原則。A線程中調用threadB.start()方法,那么threadB.start()方法會早于B線程中中的任何動作執行。
5. 傳遞規則。如果A早于B執行,B早于C執行,那么A一定早于C執行。
6. 線程中斷規則:線程interrupt()方法的一定早于檢測到線程的中斷信號。
7. 線程終結規則:如果線程A終結了,并且導致另外一個線程B中的ThreadA.join()方法取得返回,那么線程A中所有的操作都早于線程B在ThreadA.join()之后的動作發生。
8. 對象終結規則:一個對象初始化操作肯定先于它的finalize()方法。
我們只有充分理解了happens-before原則,才能在編寫多線程程序的時候,盡量避免數據的不一致性,讓多線程程序在必要的時候按照我們設計的次序執行。
## 4\. 總結
JMM是程序執行順序的指導原則,通過JMM的約束,我們能夠設計出符合我們要求的多線程程序。其實不止多線程,單線程程序一樣受到JMM的約束。
- 前言
- 第1章 Java并發簡介
- 01 開篇詞:多線程為什么是你必需要掌握的知識
- 02 絕對不僅僅是為了面試—我們為什么需要學習多線程
- 03 多線程開發如此簡單—Java中如何編寫多線程程序
- 04 人多力量未必大—并發可能會遇到的問題
- 第2章 Java中如何編寫多線程
- 05 看若兄弟,實如父子—Thread和Runnable詳解
- 06 線程什么時候開始真正執行?—線程的狀態詳解
- 07 深入Thread類—線程API精講
- 08 集體協作,什么最重要?溝通!—線程的等待和通知
- 09 使用多線程實現分工、解耦、緩沖—生產者、消費者實戰
- 第3章 并發的問題和原因詳解
- 10 有福同享,有難同當—原子性
- 11 眼見不實—可見性
- 12 什么?還有這種操作!—有序性
- 13 問題的根源—Java內存模型簡介
- 14 僵持不下—死鎖詳解
- 第4章 如何解決并發問題
- 15 原子性輕量級實現—深入理解Atomic與CAS
- 16 讓你眼見為實—volatile詳解
- 17 資源有限,請排隊等候—Synchronized使用、原理及缺陷
- 18 線程作用域內共享變量—深入解析ThreadLocal
- 第5章 線程池
- 19 自己動手豐衣足食—簡單線程池實現
- 20 其實不用造輪子—Executor框架詳解
- 第6章 主要并發工具類
- 21 更高級的鎖—深入解析Lock
- 22 到底哪把鎖更適合你?—synchronized與ReentrantLock對比
- 23 按需上鎖—ReadWriteLock詳解
- 24 經典并發容器,多線程面試必備—深入解析ConcurrentHashMap上
- 25 經典并發容器,多線程面試必備—深入解析ConcurrentHashMap下
- 26不讓我進門,我就在門口一直等!—BlockingQueue和ArrayBlockingQueue
- 27 倒數計時開始,三、二、一—CountDownLatch詳解
- 28 人齊了,一起行動—CyclicBarrier詳解
- 29 一手交錢,一手交貨—Exchanger詳解
- 30 限量供應,不好意思您來晚了—Semaphore詳解
- 第7章 高級并發工具類及并發設計模式
- 31 憑票取餐—Future模式詳解
- 32 請按到場順序發言—Completion Service詳解
- 33 分階段執行你的任務-學習使用Phaser運行多階段任務
- 34 誰都不能偷懶-通過 CompletableFuture 組裝你的異步計算單元
- 35 拆分你的任務—學習使用Fork/Join框架
- 36 為多線程們安排一位經理—Master/Slave模式詳解
- 第8章 總結
- 37 結束語