計算機的 CPU
和內存之間一直有一個核心矛盾,就是它們之間的運算速度有好幾個數量級的差距,爲了平衡它們的差別,主要作了如下:數組
CPU
增長了高速緩存,以均衡與內存的速度差別;CPU
,均衡 CPU
與 I/O
設備的差別;雖然高速緩存很好地解決了處理器與內存的速度矛盾,可是又出現了一個新的問題。在多核處理機中,每一個處理器都有本身的高速緩存,它們共享同一主內存。當多個處理器的任務涉及到同一塊主內存區域時,可能致使緩存的數據不一致的狀況,這就是可見性問題,可見性是指一個線程對共享變量的修改,另一個線程可以馬上看到。緩存
操做系統基於線程來進行任務調度。高級語言的一條語句每每須要多條指令完成,可是任務切換能夠發生在任何一條 CPU
指令後,在多線程環境下這就可能致使數據與預期的不一致,即原子性問題。原子性是指一個或多個操做在 CPU
執行過程當中不被中斷。安全
編譯器的指令重排序優化一樣不能保證最終的結果與預期的一致。這裏的重排序會知足如下兩個條件:多線程
as-if-serial
:在單線程環境下無論怎麼重排序,不能改變程序運行的結果。須要注意的是:雖然重排序不會影響單線程環境的執行結果,可是會破壞多線程的執行語義。也就是有序性問題,有序性指的是程序按照代碼的前後順序(邏輯前後)執行。併發
因此,JVM
試圖虛擬機定義了一種 Java
內存模型(Java Memory Model
,JMM
)來屏蔽掉各層硬件和操做系統的內存訪問差別,以實現讓 Java
程序在各類平臺下都能達到一致的內存訪問效果,也就是解決以上三個問題。app
Java
內存模型主要是爲了定義程序中各個變量的訪問規則,此處的變量指的是實例字段、靜態字段和構成數組對象的元素等共享變量。學習
Java
內存模型規定了全部的變量都存儲在主內存中。每一個線程還有本身的工做內存,其中保存了該線程使用的變量的主內存副本拷貝,線程對變量的全部操做必須在工做內存中進行。不一樣線程之間也沒法直接訪問對方工做內存中的變量,線程之間的變量值傳遞須要經過主內存來完成。優化
線程、工做內存、主內存三者的關係以下:this
對於主內存與工做內存之間交互的實現細節,Java
內存模型中定義了 8
種操做來實現,虛擬機實現時必須保證這些操做是原子性的。操作系統
read
(讀取):把一個變量的值從主內存傳輸到工做內存中;load
(載入):把 read
操做從主內存獲得的變量放入工做內存的變量副本中;use
(使用);把工做內存中一個變量的值傳遞給執行引擎;assign
(使用):把一個從執行引擎接收到的值賦給工做內存的變量;store
(存儲):把工做內存中一個變量的值傳送到主內存中;write
(寫入):把 store
操做從工做內存中獲得的變量放入主內存的變量中。關鍵字 volatile
是 JVM
提供的輕量級的同步機制。當一個變量被定義爲 volatile
後,它能夠保證內存的可見性。
使用 volatile
還能夠禁止指令重排序優化。它是 Java
編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。
內存屏障(Memory Barrier
)是一組處理器指令,用於實現對內存訪問操做的順序限制。在重排序時不能把後面的指令重排序到內存屏障以前的位置。
Java
內存模型保證了併發的三個特性:原子性、可見性、有序性,下面學習一下哪些操做實現了這三個特性:
Java
內存模型保證了內存間交互的 8
個操做的原子性,但對於 64
位的數據類型(long
和 double
),容許虛擬機的實現能夠不保證 64
位數據類型的 load
、store
、read
和 write
這 4
個操做的原子性。但目前虛擬機幾乎都把 64
位數據的讀寫操縱做爲原子性來對待。也就是說能夠認爲基本類型的讀寫訪問是具有原子性的。
JMM
還提供了 lock
和 unlock
操做來保證更大範圍的原子性,儘管虛擬機並未將其開放給用戶,但可以使用 monitorenter
和 monitorexit
字節碼指令來隱式地使用這兩個操做,對應到 Java
代碼中就是 synchronized
關鍵字,因此 synchronized
同步塊也是原子性的。
主要有三種方式實現可見性:
volatile
:volatile
保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。synchronized
:對同步塊加鎖解鎖,在執行 unlock
操做前必須把此變量值同步到主內存中。final
:被 final
關鍵字修飾的字段在構造器中一旦初始化完成,而且沒有發生 this
引用逃逸(其它線程可能經過引用訪問到初始化了一半的對象),那麼其它線程就能看見 final
字段的值。Java
中有兩種方式保證線程之間操做的有序性;
volatile
關鍵字經過添加內存屏障的方式來禁止指令重排。synchronized
來保證有序性,它保證每一個時刻只有一個線程執行同步代碼,即讓線程串行地執行同步代碼。前面說的保證併發安全的定義實踐起來比較麻煩,有一個等效判斷原則——Happens-Before
原則,來肯定一個訪問在併發環境下是否安全。
Happens-Before
的含義就是前面一個操做的結果對後續操做是可見的。要想保證執行操做 B
的線程看到線程 A
的結果,那麼 A
和 B
之間必須知足 Happens-Before
原則。若是兩個操做之間缺少 Happens-Before
原則,那麼 JVM
就能夠對它們任意地重排序,那麼就會產生數據競爭問題。
Happens-Before
原則包括:
Happens-Before
於後面的操做。unlock
操做 Happens-Before
於後面對同一個鎖的 lock
操做。volatile
變量規則:對一個 volatile
變量的寫操做 Happens-Before
於對該變量的讀操做。Thread
對象的 start
方法 Happens-Before
於此線程的每個動做。Happens-Before
於對該線程的終止檢測,可經過 Thread.join
方法結束,或 Thread.isAlive
方法的返回值,檢測到線程已經終止執行。interrupt
方法的調用 Happens-Before
於被中斷線程的代碼檢測到中斷事件的發生。Happens-Before
於它的 finalize
方法的開始。A
Happens-Before
於操做 B
,操做 B
Happens-Before
於操做 C
,那麼操做 A
就 Happens-Before
於操做 C
。